Add project files.
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Sarand.Net.Standard.Base.Helpers
|
||||
{
|
||||
//==========================================================================
|
||||
public static class SacsContentTypeHelper
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Definition - Content Type Data.
|
||||
public class ContentTypeData
|
||||
{
|
||||
protected ContentTypeData(List<String> extensions, String caption, String contentType, Sacs.MimeType mime)
|
||||
{
|
||||
ContentType = contentType;
|
||||
Caption = caption;
|
||||
Extensions.AddRange(extensions);
|
||||
Mime = mime;
|
||||
}
|
||||
public String PreferredContentType { get; protected set; }
|
||||
public String ContentType { get; }
|
||||
public String Caption { get; }
|
||||
public List<String> Extensions { get; } = new List<string>();
|
||||
public Sacs.MimeType Mime { get; }
|
||||
}
|
||||
private class ContentTypeData_Internal : ContentTypeData
|
||||
{
|
||||
public ContentTypeData_Internal(String extension, String caption, String contentType, Sacs.MimeType mime = Sacs.MimeType.Unknown) :
|
||||
base(StringListUtil.Split(extension, ",", true, true, true), caption, contentType, mime)
|
||||
{ }
|
||||
public new String PreferredContentType { set { base.PreferredContentType = value; } }
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
private static readonly List<ContentTypeData> contentTypes;
|
||||
private static FileExtensionContentTypeProvider provider = null;
|
||||
private static readonly HashSet<Sacs.MimeType> allowedMimeTypes_CandidateCV = new HashSet<Sacs.MimeType>()
|
||||
{
|
||||
Sacs.MimeType.MsDoc,
|
||||
Sacs.MimeType.MsDocx,
|
||||
Sacs.MimeType.OpenDocText,
|
||||
Sacs.MimeType.Pdf,
|
||||
Sacs.MimeType.Rtf
|
||||
};
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Static initialisation.
|
||||
static SacsContentTypeHelper()
|
||||
{
|
||||
// A few useful content types for reference.
|
||||
contentTypes = new List<ContentTypeData>()
|
||||
{
|
||||
// Data.
|
||||
new ContentTypeData_Internal("txt", "Text, (generally ASCII or ISO 8859-n)", "text/plain", Sacs.MimeType.Text),
|
||||
new ContentTypeData_Internal("csv", "Comma separated variable", "text/csv", Sacs.MimeType.Csv),
|
||||
new ContentTypeData_Internal("xml", "XML (not readable from casual users)", "application/xml", Sacs.MimeType.Xml) { PreferredContentType = "text/xml" },
|
||||
new ContentTypeData_Internal("xml", "XML (readable from casual users)", "text/xml", Sacs.MimeType.Xml),
|
||||
new ContentTypeData_Internal("zip", "ZIP archive", "application/x-zip-compressed", Sacs.MimeType.Zip),
|
||||
new ContentTypeData_Internal("zip", "ZIP archive", "application/zip", Sacs.MimeType.Zip) { PreferredContentType = "application/x-zip-compressed" },
|
||||
new ContentTypeData_Internal("bin", "Generic binary data", "application/octet-stream", Sacs.MimeType.Bin),
|
||||
// Web.
|
||||
new ContentTypeData_Internal("html,htm", "HyperText Markup Language (HTML)", "text/html", Sacs.MimeType.Html),
|
||||
new ContentTypeData_Internal("css", "Cascading Style Sheets (CSS)", "text/css", Sacs.MimeType.Css),
|
||||
new ContentTypeData_Internal("js", "JavaScript", "application/javascript", Sacs.MimeType.Javascript),
|
||||
new ContentTypeData_Internal("js", "JavaScript", "text/javascript", Sacs.MimeType.Javascript) { PreferredContentType = "application/javascript" },
|
||||
new ContentTypeData_Internal("json", "JSON format", "application/json", Sacs.MimeType.Json),
|
||||
// Images.
|
||||
new ContentTypeData_Internal("png", "Portable Network Graphics", "image/png", Sacs.MimeType.Png),
|
||||
new ContentTypeData_Internal("jpg,jpeg", "JPEG images", "image/jpeg", Sacs.MimeType.Jpeg),
|
||||
new ContentTypeData_Internal("bmp", "Windows OS/2 Bitmap Graphics", "image/bmp", Sacs.MimeType.Bmp),
|
||||
new ContentTypeData_Internal("gif", "Graphics Interchange Format (GIF)", "image/gif", Sacs.MimeType.Gif),
|
||||
new ContentTypeData_Internal("tif,tiff", "Tagged Image File Format (TIFF)", "image/tiff", Sacs.MimeType.Tif),
|
||||
// Documents.
|
||||
new ContentTypeData_Internal("pdf", "Adobe Portable Document Format (PDF)", "application/pdf", Sacs.MimeType.Pdf),
|
||||
// Text documents.
|
||||
new ContentTypeData_Internal("rtf", "Rich text format file", "application/rtf", Sacs.MimeType.Rtf),
|
||||
new ContentTypeData_Internal("me", "ReadMe text file", "application/x-troff-me"),
|
||||
new ContentTypeData_Internal("doc", "Microsoft Word", "application/msword", Sacs.MimeType.MsDoc),
|
||||
new ContentTypeData_Internal("docx", "Microsoft Word (OpenXML)", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", Sacs.MimeType.MsDocx),
|
||||
new ContentTypeData_Internal("odt", "OpenDocument text document", "application/vnd.oasis.opendocument.text", Sacs.MimeType.OpenDocText),
|
||||
// Spreadsheets.
|
||||
new ContentTypeData_Internal("xls", "Microsoft Excel", "application/vnd.ms-excel", Sacs.MimeType.MsXls),
|
||||
new ContentTypeData_Internal("xlsx", "Microsoft Excel (OpenXML)", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", Sacs.MimeType.MsXlsx),
|
||||
new ContentTypeData_Internal("ods", "OpenDocument spreadsheet document", "application/vnd.oasis.opendocument.spreadsheet", Sacs.MimeType.OpenDocSpreadsheet),
|
||||
// Miscellaneous.
|
||||
new ContentTypeData_Internal("aac", "AAC audio", "audio/aac"),
|
||||
new ContentTypeData_Internal("abw", "AbiWord document", "application/x-abiword"),
|
||||
new ContentTypeData_Internal("arc", "Archive document (multiple files embedded)", "application/x-freearc"),
|
||||
new ContentTypeData_Internal("avi", "AVI: Audio Video Interleave", "video/x-msvideo"),
|
||||
new ContentTypeData_Internal("azw", "Amazon Kindle eBook format", "application/vnd.amazon.ebook"),
|
||||
new ContentTypeData_Internal("bz", "BZip archive", "application/x-bzip"),
|
||||
new ContentTypeData_Internal("bz2", "BZip2 archive", "application/x-bzip2"),
|
||||
new ContentTypeData_Internal("csh", "C-Shell script", "application/x-csh"),
|
||||
new ContentTypeData_Internal("csv", "Comma-separated values (CSV)", "text/csv"),
|
||||
new ContentTypeData_Internal("eot", "MS Embedded OpenType fonts", "application/vnd.ms-fontobject"),
|
||||
new ContentTypeData_Internal("epub", "Electronic publication (EPUB)", "application/epub+zip"),
|
||||
new ContentTypeData_Internal("gz", "GZip Compressed Archive", "application/x-gzip"),
|
||||
new ContentTypeData_Internal("gz", "GZip Compressed Archive", "application/gzip") { PreferredContentType = "application/x-gzip" },
|
||||
new ContentTypeData_Internal("ico", "Icon format", "image/x-icon"),
|
||||
new ContentTypeData_Internal("ico", "Icon format", "image/vnd.microsoft.icon") { PreferredContentType = "image/x-icon" },
|
||||
new ContentTypeData_Internal("ics", "iCalendar format", "text/calendar"),
|
||||
new ContentTypeData_Internal("jar", "Java Archive (JAR)", "application/java-archive"),
|
||||
new ContentTypeData_Internal("jsonld", "JSON-LD format", "application/ld+json"),
|
||||
new ContentTypeData_Internal("midi,mid", "Musical Instrument Digital Interface (MIDI)", "audio/mid"),
|
||||
new ContentTypeData_Internal("midi,mid", "Musical Instrument Digital Interface (MIDI)", "audio/midi") { PreferredContentType = "audio/mid" },
|
||||
new ContentTypeData_Internal("midi,mid", "Musical Instrument Digital Interface (MIDI)", "audio/x-midi") { PreferredContentType = "audio/mid" },
|
||||
new ContentTypeData_Internal("mjs", "JavaScript module", "text/javascript"),
|
||||
new ContentTypeData_Internal("mp3", "MP3 audio", "audio/mpeg"),
|
||||
new ContentTypeData_Internal("mpeg", "MPEG Video", "video/mpeg"),
|
||||
new ContentTypeData_Internal("mpkg", "Apple Installer Package", "application/vnd.apple.installer+xml"),
|
||||
new ContentTypeData_Internal("odp", "OpenDocument presentation document", "application/vnd.oasis.opendocument.presentation"),
|
||||
new ContentTypeData_Internal("oga", "OGG audio", "audio/ogg"),
|
||||
new ContentTypeData_Internal("ogv", "OGG video", "video/ogg"),
|
||||
new ContentTypeData_Internal("ogx", "OGG", "application/ogg"),
|
||||
new ContentTypeData_Internal("opus", "Opus audio", "audio/opus"),
|
||||
new ContentTypeData_Internal("otf", "OpenType font", "font/otf"),
|
||||
new ContentTypeData_Internal("php", "Hypertext Preprocessor (Personal Home Page)", "application/x-httpd-php"),
|
||||
new ContentTypeData_Internal("ppt", "Microsoft PowerPoint", "application/vnd.ms-powerpoint"),
|
||||
new ContentTypeData_Internal("pptx", "Microsoft PowerPoint (OpenXML)", "application/vnd.openxmlformats-officedocument.presentationml.presentation"),
|
||||
new ContentTypeData_Internal("rar", "RAR archive", "application/vnd.rar"),
|
||||
new ContentTypeData_Internal("rtf", "Rich Text Format (RTF)", "application/rtf"),
|
||||
new ContentTypeData_Internal("sh", "Bourne shell script", "application/x-sh"),
|
||||
new ContentTypeData_Internal("svg", "Scalable Vector Graphics (SVG)", "image/svg+xml"),
|
||||
new ContentTypeData_Internal("swf", "Small web format (SWF) or Adobe Flash document", "application/x-shockwave-flash"),
|
||||
new ContentTypeData_Internal("tar", "Tape Archive (TAR)", "application/x-tar"),
|
||||
new ContentTypeData_Internal("ts", "MPEG transport stream", "video/vnd.dlna.mpeg-tts"),
|
||||
new ContentTypeData_Internal("ts", "MPEG transport stream", "video/mp2t") { PreferredContentType = "video/vnd.dlna.mpeg-tts" },
|
||||
new ContentTypeData_Internal("ttf", "TrueType Font", "application/x-font-ttf"),
|
||||
new ContentTypeData_Internal("ttf", "TrueType Font", "font/ttf") { PreferredContentType = "application/x-font-ttf" },
|
||||
new ContentTypeData_Internal("vsd", "Microsoft Visio", "application/vnd.visio"),
|
||||
new ContentTypeData_Internal("wav", "Waveform Audio Format", "audio/wav"),
|
||||
new ContentTypeData_Internal("webm", "WEBM video", "video/webm"),
|
||||
new ContentTypeData_Internal("weba", "WEBM audio", "audio/webm"),
|
||||
new ContentTypeData_Internal("webp", "WEBP image", "image/webp"),
|
||||
new ContentTypeData_Internal("woff", "Web Open Font Format (WOFF)", "application/font-woff"),
|
||||
new ContentTypeData_Internal("woff", "Web Open Font Format (WOFF)", "font/woff") { PreferredContentType = "application/font-woff" },
|
||||
new ContentTypeData_Internal("woff2", "Web Open Font Format (WOFF)", "font/woff2"),
|
||||
new ContentTypeData_Internal("xhtml", "XHTML", "application/xhtml+xml"),
|
||||
new ContentTypeData_Internal("xul", "XUL", "application/vnd.mozilla.xul+xml"),
|
||||
new ContentTypeData_Internal("3gp", "3GPP video container", "video/3gpp"),
|
||||
new ContentTypeData_Internal("3gp", "3GPP audio container", "audio/3gpp") { PreferredContentType = "video/3gpp" },
|
||||
new ContentTypeData_Internal("3g2", "3GPP2 video container", "video/3gpp2"),
|
||||
new ContentTypeData_Internal("3g2", "3GPP2 audio container", "audio/3gpp2") { PreferredContentType = "video/3gpp2" },
|
||||
new ContentTypeData_Internal("7z", "7-zip archive", "application/x-7z-compressed")
|
||||
};
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Properties.
|
||||
/// <summary>Generic content type used for 'unknown' document types.</summary>
|
||||
public static String ContentTypeDefault { get; } = "application/octet-stream";
|
||||
/// <summary>Selection of comon content types.</summary>
|
||||
public static List<ContentTypeData> ContentTypes { get { return new List<ContentTypeData>(contentTypes); } }
|
||||
public static FileExtensionContentTypeProvider Provider
|
||||
{
|
||||
get
|
||||
{
|
||||
if (provider == null)
|
||||
provider = new FileExtensionContentTypeProvider();
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>Generic content types that might have a better preferred content type when converting, eg. from extension.</summary>
|
||||
private static readonly List<String> genericContentTypes = new List<string>() { "application/octet-stream", "application/oleobject" };
|
||||
/// <summary>
|
||||
/// Given a file path, determine the MIME type.
|
||||
/// <para>Ref. NuGet package Microsoft.AspNetCore.StaticFiles</para>
|
||||
/// </summary>
|
||||
public static String GetContentTypeFromFilename(this String fileName)
|
||||
{
|
||||
string contentType = "";
|
||||
if (!Provider.TryGetContentType(fileName, out contentType) || genericContentTypes.ContainsCI(contentType))
|
||||
{
|
||||
// Locate suitable 'common' content type definition.
|
||||
String extension = Regex.Replace(Path.GetExtension(fileName), "^\\.", "");
|
||||
// Use the 'primary' definition for the extension, with no 'preferred' content type.
|
||||
ContentTypeData contentTypeData = contentTypes.Find(item => item.Extensions.ContainsCI(extension) && StringUtil.IsEmpty(item.PreferredContentType));
|
||||
if (contentTypeData != null)
|
||||
contentType = contentTypeData.ContentType;
|
||||
}
|
||||
if (StringUtil.IsEmpty(contentType))
|
||||
contentType = ContentTypeDefault;
|
||||
return contentType;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Interpret the content type for the specified file.
|
||||
/// <para>- The file's content type if not generic (application/octet-stream, application/oleobject).</para>
|
||||
/// <para>- The content type derived from the file's extension if available</para>
|
||||
/// <para>- Generic content type: application/octet-stream.</para>
|
||||
/// </summary>
|
||||
public static String GetContentTypeFromFormFile(String contentType, String fileName)
|
||||
{
|
||||
// Return file's content type if specific.
|
||||
if (!StringUtil.IsEmpty(contentType) && !genericContentTypes.ContainsCI(contentType))
|
||||
return contentType;
|
||||
// Otherwise, interpret filename's content type.
|
||||
if (!StringUtil.IsEmpty(fileName))
|
||||
return SacsContentTypeHelper.GetContentTypeFromFilename(fileName);
|
||||
return ContentTypeDefault;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interpret the content type for the specified file.
|
||||
/// <para>- The file's content type if not generic (application/octet-stream, application/oleobject).</para>
|
||||
/// <para>- The content type derived from the file's extension if available</para>
|
||||
/// <para>- Generic content type: application/octet-stream.</para>
|
||||
/// </summary>
|
||||
public static String GetContentTypeFromFormFile(this IFormFile formFile)
|
||||
{
|
||||
return GetContentTypeFromFormFile(formFile.ContentType, formFile.FileName);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
/// <summary>Convert the content type to enumerated Mime.</summary>
|
||||
public static Sacs.MimeType GetMimeTypeFromContentType(this String contentType)
|
||||
{
|
||||
if (StringUtil.IsEmpty(contentType)) return Sacs.MimeType.Unknown;
|
||||
ContentTypeData contentTypeData = contentTypes.Find(item => item.ContentType.EqualsCI(contentType));
|
||||
if (contentTypeData != null)
|
||||
return contentTypeData.Mime;
|
||||
return Sacs.MimeType.Unknown;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
}
|
||||
//==========================================================================
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
using CsvHelper;
|
||||
using CsvHelper.Configuration;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Sarand.Net.Standard.Base.Helpers
|
||||
{
|
||||
//=======================================================================
|
||||
public static class SacsCsvHelper
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Definitions.
|
||||
/// <summary>
|
||||
/// Encapsulate CSV Reader and the text object that it is to parse.
|
||||
/// </summary>
|
||||
private class ReaderClass : SacsDisposable
|
||||
{
|
||||
public ReaderClass() : this(DefaultCulture) { }
|
||||
public ReaderClass(CultureInfo culture)
|
||||
{
|
||||
Reader = new StringReader("");
|
||||
CsvReader = new CsvReader(Reader, culture ?? DefaultCulture);
|
||||
}
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
CsvReader.Dispose();
|
||||
Reader.Dispose();
|
||||
}
|
||||
}
|
||||
public StringReader Reader { get; }
|
||||
public CsvReader CsvReader { get; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Encapsulate CSV Writer and the text stream that it is to generate.
|
||||
/// </summary>
|
||||
private class WriterClass : SacsDisposable
|
||||
{
|
||||
// Construction.
|
||||
public WriterClass() : this(DefaultCulture) { }
|
||||
public WriterClass(CultureInfo culture)
|
||||
{
|
||||
Writer = new StringWriter() { NewLine = "" };
|
||||
CsvWriter = new CsvWriter(Writer, culture ?? DefaultCulture);
|
||||
}
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
CsvWriter.Dispose();
|
||||
Writer.Dispose();
|
||||
}
|
||||
}
|
||||
// Properties.
|
||||
public StringWriter Writer { get; }
|
||||
public CsvWriter CsvWriter { get; }
|
||||
// To CSV.
|
||||
public String ToCsv(IEnumerable<String> values)
|
||||
{
|
||||
foreach (String field in values)
|
||||
CsvWriter.WriteField(field);
|
||||
CsvWriter.NextRecord();
|
||||
// NB Shouldn't need to strip line delimiters as the writer's NewLine is blank but CsvHelper v16 still adds the delimiter.
|
||||
String record = Writer.GetStringBuilder().ToString().Replace("\r", "").Replace("\n", "");
|
||||
// Clear the String Writer data. [Ref. https://stackoverflow.com/questions/12839982/clear-all-content-in-xmltextwriter-and-stringwriter]
|
||||
Writer.GetStringBuilder().Clear();
|
||||
return record;
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Properties.
|
||||
public static CultureInfo DefaultCulture { get; set; } = CultureInfo.InvariantCulture;
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// C o n f i g u r a t i o n D e f a u l t s
|
||||
//-------------------------------------------------------------------
|
||||
// AllowComments false bool
|
||||
// BadDataFound {Method = {Void BadDataFound(CsvHelper.ReadingContext)}} System.Action<CsvHelper.ReadingContext>
|
||||
// BufferSize 2048 int
|
||||
// Comment 35 '#' char
|
||||
// CountBytes false bool
|
||||
// CultureInfo {en-GB} System.Globalization.CultureInfo
|
||||
// Delimiter "," string
|
||||
// DetectColumnCountChanges false bool
|
||||
// DoubleQuoteString "\"\"" string
|
||||
// DynamicPropertySort null System.Collections.Generic.IComparer<string>
|
||||
// Encoding {System.Text.UTF8Encoding} System.Text.Encoding {System.Text.UTF8Encoding}
|
||||
// Escape 34 '"' char
|
||||
// GetConstructor {Method = {System.Reflection.ConstructorInfo GetConstructor(System.Type)}} System.Func<System.Type, System.Reflection.ConstructorInfo>
|
||||
// HasHeaderRecord true bool
|
||||
// HeaderValidated {Method = {Void HeaderValidated(Boolean, System.String[], Int32, CsvHelper.ReadingContext)}} System.Action<bool, string[], int, CsvHelper.ReadingContext>
|
||||
// IgnoreBlankLines true bool
|
||||
// IgnoreQuotes false bool
|
||||
// IgnoreReferences false bool
|
||||
// IncludePrivateMembers false bool
|
||||
// InjectionCharacters {char[4]} char[]
|
||||
// InjectionEscapeCharacter 9 '\t' char
|
||||
// LineBreakInQuotedFieldIsBadData false bool
|
||||
// Maps {CsvHelper.Configuration.ClassMapCollection} CsvHelper.Configuration.ClassMapCollection
|
||||
// MemberTypes Properties CsvHelper.Configuration.MemberTypes
|
||||
// MissingFieldFound {Method = {Void MissingFieldFound(System.String[], Int32, CsvHelper.ReadingContext)}} System.Action<string[], int, CsvHelper.ReadingContext>
|
||||
// PrepareHeaderForMatch {Method = {System.String PrepareHeaderForMatch(System.String, Int32)}} System.Func<string, int, string>
|
||||
// Quote 34 '"' char
|
||||
// QuoteString "\"" string
|
||||
// ReadingExceptionOccurred {Method = {Boolean ReadingExceptionOccurred(CsvHelper.CsvHelperException)}} System.Func<CsvHelper.CsvHelperException, bool>
|
||||
// ReferenceHeaderPrefix null System.Func<System.Type, string, string>
|
||||
// SanitizeForInjection false bool
|
||||
// ShouldQuote {Method = {Boolean ShouldQuote(System.String, CsvHelper.WritingContext)}} System.Func<string, CsvHelper.WritingContext, bool>
|
||||
// ShouldSkipRecord {Method = {Boolean ShouldSkipRecord(System.String[])}} System.Func<string[], bool>
|
||||
// ShouldUseConstructorParameters {Method = {Boolean ShouldUseConstructorParameters(System.Type)}} System.Func<System.Type, bool>
|
||||
// TrimOptions None CsvHelper.Configuration.TrimOptions
|
||||
// TypeConverterCache {CsvHelper.TypeConversion.TypeConverterCache} CsvHelper.TypeConversion.TypeConverterCache
|
||||
// TypeConverterOptionsCache {CsvHelper.TypeConversion.TypeConverterOptionsCache} CsvHelper.TypeConversion.TypeConverterOptionsCache
|
||||
// UseNewObjectForNullReferenceMembers true bool
|
||||
//-------------------------------------------------------------------
|
||||
public class ParserConfig /* IParserConfiguration */
|
||||
{
|
||||
/// <summary>Culture</summary>
|
||||
public CultureInfo Culture { get; set; } = CultureInfo.InvariantCulture;
|
||||
/// <summary>Gets or sets the size of the buffer used for reading CSV files. Default: 2048.</summary>
|
||||
public int BufferSize { get; set; } = 2048;
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the number of bytes should be counted while parsing.
|
||||
/// <para>This will slow down parsing because it needs to get the byte count of every char for the given encoding.</para>
|
||||
/// <para>The CsvHelper.Configuration.IParserConfiguration.Encoding needs to be set correctly for this to be accurate.</para>
|
||||
/// <para>Default: false.</para>
|
||||
/// </summary>
|
||||
public bool CountBytes { get; set; } = false;
|
||||
/// <summary>Gets or sets the encoding used when counting bytes.</summary>
|
||||
public Encoding Encoding { get; set; } = Encoding.UTF8;
|
||||
/// <summary>
|
||||
/// Gets or sets the function that is called when bad field data is found.
|
||||
/// <para>A field has bad data if it contains a quote and the field is not quoted (escaped).</para>
|
||||
/// <para>You can supply your own function to do other things like logging the issue instead of throwing an exception.</para>
|
||||
/// <para>Arguments: context</para>
|
||||
/// </summary>
|
||||
public Action<ReadingContext> BadDataFound { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating if a line break found in a quote field should be considered bad data.
|
||||
/// <para>True to consider a line break bad data, otherwise false.</para>
|
||||
/// <para>Defaults to false.</para>
|
||||
/// </summary>
|
||||
public bool LineBreakInQuotedFieldIsBadData { get; set; } = false;
|
||||
/// <summary>
|
||||
/// Gets or sets the character used to denote a line that is commented out.
|
||||
/// <para>Default: '#'.</para>
|
||||
/// </summary>
|
||||
public char Comment { get; set; } = '#';
|
||||
/// <summary>
|
||||
/// <para>Gets or sets a value indicating if comments are allowed.</para>
|
||||
/// <para>True to allow commented out lines, otherwise false (default).</para></summary>
|
||||
public bool AllowComments { get; set; } = false;
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating if blank lines should be ignored when reading.
|
||||
/// <para>True to ignore, otherwise false.</para>
|
||||
/// <para>Default: true.</para>
|
||||
/// </summary>
|
||||
public bool IgnoreBlankLines { get; set; } = true;
|
||||
/// <summary>Gets or sets a value indicating if quotes should be ingored when parsing and treated like any other character.</summary>
|
||||
public bool IgnoreQuotes { get; set; } = false;
|
||||
/// <summary>
|
||||
/// Gets or sets the character used to quote fields.
|
||||
/// <para>Default: '"'.</para>
|
||||
/// </summary>
|
||||
public char Quote { get; set; } = '"';
|
||||
/// <summary>
|
||||
/// Gets or sets the delimiter used to separate fields.
|
||||
/// <para>Default: CultureInfo.CurrentCulture.TextInfo.ListSeparator (',').</para>
|
||||
/// </summary>
|
||||
public string Delimiter { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the escape character used to escape a quote inside a field.
|
||||
/// <para>Default: '"'.</para>
|
||||
/// </summary>
|
||||
public char Escape { get; set; } = '"';
|
||||
/// <summary>
|
||||
/// Gets or sets the field trimming options.
|
||||
/// <para>Default: None</para>
|
||||
/// </summary>
|
||||
public TrimOptions TrimOptions { get; set; } = TrimOptions.None;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// C o n v e r t T o C S V
|
||||
//-------------------------------------------------------------------
|
||||
public static List<String[]> ParseCsv(String data, ParserConfig parserConfig = null)
|
||||
{
|
||||
List<String[]> records = new List<string[]>();
|
||||
|
||||
using (TextReader reader = new StringReader(data))
|
||||
using (CsvParser csvParser = new CsvParser(reader, parserConfig?.Culture ?? CultureInfo.InvariantCulture))
|
||||
{
|
||||
if (parserConfig != null)
|
||||
{
|
||||
if (parserConfig.BufferSize > 0)
|
||||
csvParser.Configuration.BufferSize = parserConfig.BufferSize;
|
||||
csvParser.Configuration.CountBytes = parserConfig.CountBytes;
|
||||
csvParser.Configuration.Encoding = parserConfig.Encoding;
|
||||
if (parserConfig.BadDataFound != null)
|
||||
csvParser.Configuration.BadDataFound = parserConfig.BadDataFound;
|
||||
csvParser.Configuration.LineBreakInQuotedFieldIsBadData = parserConfig.LineBreakInQuotedFieldIsBadData;
|
||||
csvParser.Configuration.Comment = parserConfig.Comment;
|
||||
csvParser.Configuration.AllowComments = parserConfig.AllowComments;
|
||||
csvParser.Configuration.IgnoreBlankLines = parserConfig.IgnoreBlankLines;
|
||||
csvParser.Configuration.IgnoreQuotes = parserConfig.IgnoreQuotes;
|
||||
csvParser.Configuration.Quote = parserConfig.Quote;
|
||||
if (parserConfig.Delimiter != null)
|
||||
csvParser.Configuration.Delimiter = parserConfig.Delimiter;
|
||||
csvParser.Configuration.Escape = parserConfig.Escape;
|
||||
csvParser.Configuration.TrimOptions = parserConfig.TrimOptions;
|
||||
}
|
||||
while (true)
|
||||
{
|
||||
String[] record = csvParser.Read();
|
||||
if (record == null) break;
|
||||
records.Add(record);
|
||||
}
|
||||
}
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// C o n v e r t T o C S V
|
||||
//-------------------------------------------------------------------
|
||||
// Single record.
|
||||
public static String ToCsv(IEnumerable<String> values)
|
||||
{
|
||||
return ToCsv(values, DefaultCulture);
|
||||
}
|
||||
public static String ToCsv(IEnumerable<String> values, CultureInfo culture)
|
||||
{
|
||||
String record = "";
|
||||
using (WriterClass writer = new WriterClass(culture ?? DefaultCulture))
|
||||
{
|
||||
record = writer.ToCsv(values);
|
||||
}
|
||||
return record;
|
||||
}
|
||||
//-------------------------------------------------------------------
|
||||
// Multiple records.
|
||||
public static List<String> ToCsv(IEnumerable<IEnumerable<String>> data)
|
||||
{
|
||||
return ToCsv(data, DefaultCulture);
|
||||
}
|
||||
public static List<String> ToCsv(IEnumerable<IEnumerable<String>> data, CultureInfo culture)
|
||||
{
|
||||
List<String> records = new List<string>();
|
||||
using (WriterClass writer = new WriterClass(culture ?? DefaultCulture))
|
||||
{
|
||||
foreach (String[] values in data)
|
||||
records.Add(writer.ToCsv(values));
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Sarand.Net.Standard.Base.Helpers
|
||||
{
|
||||
//=======================================================================
|
||||
public static class SacsDataValidationHelper
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Validate the email address.
|
||||
/// <para>Ref. https://stackoverflow.com/questions/1365407/c-sharp-code-to-validate-email-address</para>
|
||||
/// </summary>
|
||||
public static bool IsValidEmailAddress(String value)
|
||||
{
|
||||
if (StringUtil.IsEmpty(value)) return false;
|
||||
if (!new EmailAddressAttribute().IsValid(value)) return false;
|
||||
|
||||
// Exclude spaces.
|
||||
if (Regex.Matches(value, " ").Count > 0) return false;
|
||||
|
||||
// Exclude consecutive dots.
|
||||
if (Regex.Matches(value, "\\.\\.").Count > 0) return false;
|
||||
|
||||
// Check for at least one dot after @ [to ensure john@smith is invalid].
|
||||
if (Regex.Matches(value, "@[^@.]+\\.").Count < 1) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Validate the phone number.
|
||||
/// <para>Ref. https://www.twilio.com/blog/validating-phone-numbers-effectively-with-c-and-the-net-frameworks</para>
|
||||
/// </summary>
|
||||
public static bool IsValidPhone(String value)
|
||||
{
|
||||
if (StringUtil.IsEmpty(value)) return false;
|
||||
if (!new PhoneAttribute().IsValid(value)) return false;
|
||||
|
||||
// Apply minimum length.
|
||||
if (StringUtil.GetLength(value) < 8) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Tax identification number.
|
||||
// European country Tax Identification Numbers:
|
||||
// https://ec.europa.eu/taxation_customs/vies/faqvies.do#item_11
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Validate the tax identification number.
|
||||
/// </summary>
|
||||
public static bool IsValidTaxIdentificationNumber(String countryCode, String value)
|
||||
{
|
||||
if (StringUtil.IsEmpty(value)) return false;
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// GB, United Kingdom.
|
||||
// Standard:
|
||||
// GB999 9999 99 block of 3 digits, block of 4 digits and block of 2 digits [00-96]
|
||||
// Branch:
|
||||
// GB999 9999 99 9995 as above followed by block of 3 digits
|
||||
// Government departments:
|
||||
// GBGD9996 'GD' followed by block of 3 or 4 digits
|
||||
// Health Authoritites:
|
||||
// GBHA9997 'HA' followed by block of 3 or 4 digits
|
||||
//---------------------------------------------------------------
|
||||
// Apply minimum length.
|
||||
if (StringUtil.GetLength(value) < 8) return false;
|
||||
|
||||
// Country prefix.
|
||||
if (!value.StartsWith("GB")) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Sarand.Net.Standard.Base.Helpers
|
||||
{
|
||||
//=======================================================================
|
||||
public static class SacsEncryptionHelper
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>Number of byes in key. [32 bytes = 256 bits]</summary>
|
||||
public static int KeyLength { get; } = 32;
|
||||
/// <summary>Default encoding: UTF8.</summary>
|
||||
public static Encoding DefaultEncoding { get; } = Encoding.UTF8;
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Symmetric Encryption in .NET Core
|
||||
// System.Security.Cryptography implements and provides the APIs for encryption in .NET Core.
|
||||
// Ref. https://damienbod.com/2020/08/19/symmetric-and-asymmetric-encryption-in-net-core/
|
||||
//-------------------------------------------------------------------
|
||||
public static bool IsBase64(String value, int expectedLength = 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
Byte[] decoded = Convert.FromBase64String(value);
|
||||
if (expectedLength > 0 && decoded.Length != expectedLength)
|
||||
return false;
|
||||
}
|
||||
catch (Exception) { return false; }
|
||||
return true;
|
||||
}
|
||||
public static String EncodePlainKey(String plainKey, int keyLength = 32, Encoding encoding = null)
|
||||
{
|
||||
if (StringUtil.IsEmpty(plainKey))
|
||||
throw new NotSupportedException("Plain key must not be null/blank");
|
||||
// Ensure key if 32 bytes long.
|
||||
while (StringUtil.GetLength(plainKey) < keyLength)
|
||||
plainKey += plainKey;
|
||||
plainKey = StringUtil.Left(plainKey, keyLength);
|
||||
|
||||
// Encode to base-64.
|
||||
encoding = encoding ?? DefaultEncoding;
|
||||
return Convert.ToBase64String(encoding.GetBytes(plainKey));
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Generate random key and IV.
|
||||
// NB Can use own key if suitable - 32 character value in base-64 format.
|
||||
public static String InitSymmetricEncryptionKey()
|
||||
{
|
||||
var key = GetEncodedRandomString(KeyLength); // 256 bits.
|
||||
return key;
|
||||
}
|
||||
public static string InitSymmetricEncryptionIV(String key)
|
||||
{
|
||||
Aes cipher = CreateCipher(key);
|
||||
var IVBase64 = Convert.ToBase64String(cipher.IV);
|
||||
return IVBase64;
|
||||
}
|
||||
public static (string Key, string IVBase64) InitSymmetricEncryptionKeyIV()
|
||||
{
|
||||
var key = InitSymmetricEncryptionKey();
|
||||
var IVBase64 = InitSymmetricEncryptionIV(key);
|
||||
return (key, IVBase64);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
public static string GetEncodedRandomString(int length)
|
||||
{
|
||||
var base64 = Convert.ToBase64String(GenerateRandomBytes(length));
|
||||
return base64;
|
||||
}
|
||||
|
||||
private static Aes CreateCipher(string keyBase64)
|
||||
{
|
||||
// Default values: Keysize 256 bits, Padding PKC27.
|
||||
Aes cipher = Aes.Create();
|
||||
// Ensure the integrity of the ciphertext if using CBC
|
||||
cipher.Mode = CipherMode.CBC;
|
||||
|
||||
cipher.Padding = PaddingMode.ISO10126;
|
||||
byte[] rawKey = Convert.FromBase64String(keyBase64);
|
||||
String rawKeyValue = System.Text.Encoding.Default.GetString(rawKey);
|
||||
cipher.Key = rawKey;
|
||||
|
||||
return cipher;
|
||||
}
|
||||
|
||||
private static byte[] GenerateRandomBytes(int length)
|
||||
{
|
||||
var byteArray = new byte[length];
|
||||
SacsRandomHelper.Fill(byteArray);
|
||||
return byteArray;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
public static string Encrypt(string text, string IV, string key)
|
||||
{
|
||||
Aes cipher = CreateCipher(key);
|
||||
cipher.IV = Convert.FromBase64String(IV);
|
||||
|
||||
ICryptoTransform cryptTransform = cipher.CreateEncryptor();
|
||||
byte[] plaintext = Encoding.UTF8.GetBytes(text);
|
||||
byte[] cipherText = cryptTransform.TransformFinalBlock(plaintext, 0, plaintext.Length);
|
||||
|
||||
return Convert.ToBase64String(cipherText);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
public static string Decrypt(string encryptedText, string IV, string key)
|
||||
{
|
||||
Aes cipher = CreateCipher(key);
|
||||
cipher.IV = Convert.FromBase64String(IV);
|
||||
|
||||
ICryptoTransform cryptTransform = cipher.CreateDecryptor();
|
||||
byte[] encryptedBytes = Convert.FromBase64String(encryptedText);
|
||||
byte[] plainBytes = cryptTransform.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
|
||||
|
||||
return Encoding.UTF8.GetString(plainBytes);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Sarand.Net.Standard.Base.Tasks;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sarand.Net.Standard.Base.Helpers
|
||||
{
|
||||
//=======================================================================
|
||||
public static class SacsHttpClientHelper
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>Request configuration</summary>
|
||||
public class RequestData : SacsTaskData
|
||||
{
|
||||
// Construction.
|
||||
public RequestData(CancellationToken? cancellationToken) : base(cancellationToken) { }
|
||||
/// <summary>URL to send request to.</summary>
|
||||
public UriBuilder UriBuilder { get; } = new UriBuilder();
|
||||
/// <summary>
|
||||
/// Request timeout. Default: 5 secs.
|
||||
/// <para>NB Not currently supported as can only be changed when static HTTP Client not in use.</para>
|
||||
/// </summary>
|
||||
public int TimeoutSecs { get; set; } = RequestTimeoutSecs_Default;
|
||||
/// <summary>
|
||||
/// Headers to send with request.
|
||||
/// <para>Accept, Accept-Charset, Accept-Encoding, Accept-Language, User-Agent, Transfer-Encoding, Pragma, Expect etc.</para>
|
||||
/// </summary>
|
||||
public Dictionary<String, List<String>> RequestHeaders { get; } = new Dictionary<String, List<String>>();
|
||||
/// <summary>
|
||||
/// Content Headers to send with request.
|
||||
/// <para>Content-Length (bytes), Content-Type (media type), Content-Encoding (compression algorithm), Content-Language etc.</para>
|
||||
/// <para>Content-Type header is defaulted for POST request if not specified.</para>
|
||||
/// </summary>
|
||||
public Dictionary<String, List<String>> ContentHeaders { get; } = new Dictionary<String, List<String>>();
|
||||
/// <summary>
|
||||
/// Form values to post as 'URL Encoded'.
|
||||
/// <para>Content-Type defaults to 'application/x-www-form-urlencoded; charset=utf-8'.</para>
|
||||
/// </summary>
|
||||
public List<KeyValuePair<string, string>> FormData { get; } = new List<KeyValuePair<string, string>>();
|
||||
/// <summary>
|
||||
/// Data to POST. Ignored if FormData populated.
|
||||
/// <para>Content-Type defaults to 'text/plain; charset=utf-8'.</para>
|
||||
/// </summary>
|
||||
public String PostData { get; set; }
|
||||
/// <summary>Use POST request rather than GET even if no data to post.</summary>
|
||||
public bool ForcePost { get; set; }
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>Response data</summary>
|
||||
public class ResponseData
|
||||
{
|
||||
public DateTime RequestStarted { get; set; }
|
||||
public DateTime RequestCompleted { get; set; }
|
||||
/// <summary>Response headers.</summary>
|
||||
public Dictionary<String, IEnumerable<String>> ResponseHeaders { get; } = new Dictionary<String, IEnumerable<String>>();
|
||||
/// <summary>Response content headers.</summary>
|
||||
public Dictionary<String, IEnumerable<String>> ContentHeaders { get; } = new Dictionary<String, IEnumerable<String>>();
|
||||
/// <summary>Indicates whether Status Code represents success.</summary>
|
||||
public bool Success { get; set; }
|
||||
/// <summary>HTTP Status Code.</summary>
|
||||
public HttpStatusCode StatusCode { get; set; }
|
||||
/// <summary>HTTP message version. Default: 1.1</summary>
|
||||
public Version Version { get; set; }
|
||||
/// <summary>HTTP Reason Code.</summary>
|
||||
public String ReasonPhrase { get; set; } = "";
|
||||
/// <summary>Response data.</summary>
|
||||
public String Data { get; set; } = "";
|
||||
/// <summary>Elapsed time for request.</summary>
|
||||
public TimeSpan Elapsed { get; set; }
|
||||
/// <summary>Exception thrown during handling of request.</summary>
|
||||
public Exception Exception { get; set; }
|
||||
/// <summary>Error state message - generally exception type and message.</summary>
|
||||
public String ErrorStateMessage { get; set; }
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
private static readonly HttpClient httpClient;
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Static initialisation.
|
||||
static SacsHttpClientHelper ()
|
||||
{
|
||||
// Configure client.
|
||||
httpClient = new HttpClient();
|
||||
httpClient.Timeout = new TimeSpan(0, 0, RequestTimeoutSecs_Default);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Properties.
|
||||
public static HttpClient HttpClient { get { return httpClient; } }
|
||||
public static int RequestTimeoutSecs_Default { get; } = 5;
|
||||
public static String HeaderName_ContentType { get; } = "Content-Type";
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
public static async Task<ResponseData> DoRequest (RequestData requestData)
|
||||
{
|
||||
String fn = "SacsHttpClientHelper.DoRequest";
|
||||
requestData.ThrowIfCancellationRequested();
|
||||
ResponseData responseData = new ResponseData();
|
||||
|
||||
// Construct content to post.
|
||||
HttpContent postContent = null;
|
||||
if (requestData.FormData.Count > 0)
|
||||
{
|
||||
postContent = new FormUrlEncodedContent(requestData.FormData);
|
||||
if (!requestData.ContentHeaders.ContainsKey(HeaderName_ContentType))
|
||||
requestData.ContentHeaders.Add(HeaderName_ContentType, "application/x-www-form-urlencoded; charset=utf-8");
|
||||
}
|
||||
else if (!StringUtil.IsEmpty(requestData.PostData) || requestData.ForcePost)
|
||||
{
|
||||
postContent = new StringContent(StringUtil.EmptyOnNull(requestData.PostData), Encoding.UTF8);
|
||||
if (!requestData.ContentHeaders.ContainsKey(HeaderName_ContentType))
|
||||
requestData.ContentHeaders.Add(HeaderName_ContentType, "text/plain; charset=utf-8");
|
||||
}
|
||||
requestData.ThrowIfCancellationRequested();
|
||||
|
||||
// Make request.
|
||||
DateTime startedAt = DateTime.Now;
|
||||
try
|
||||
{
|
||||
// Request.
|
||||
using (HttpRequestMessage request = new HttpRequestMessage())
|
||||
{
|
||||
request.RequestUri = requestData.UriBuilder.Uri;
|
||||
|
||||
// Request headers.
|
||||
foreach (String headerName in requestData.RequestHeaders.Keys)
|
||||
{
|
||||
if (request.Headers.Contains(headerName))
|
||||
request.Headers.Remove(headerName);
|
||||
request.Headers.Add(headerName, requestData.RequestHeaders[headerName]);
|
||||
}
|
||||
|
||||
// Default request headers.
|
||||
if (HttpClient.DefaultRequestHeaders.Accept.Count == 0 && request.Headers.Accept.Count == 0)
|
||||
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/*"));
|
||||
|
||||
// Configure request.
|
||||
HttpResponseMessage response = null;
|
||||
// Send request.
|
||||
if (postContent != null)
|
||||
{
|
||||
// POST.
|
||||
request.Method = HttpMethod.Post;
|
||||
request.Content = postContent;
|
||||
// Content headers.
|
||||
foreach (String headerName in requestData.ContentHeaders.Keys)
|
||||
{
|
||||
if (request.Content.Headers.Contains(headerName))
|
||||
request.Content.Headers.Remove(headerName);
|
||||
request.Content.Headers.Add(headerName, requestData.ContentHeaders[headerName]);
|
||||
}
|
||||
// Send request.
|
||||
responseData.RequestStarted = DateTime.Now;
|
||||
response = await HttpClient.SendAsync(request, requestData.CancellationToken);
|
||||
responseData.RequestCompleted = DateTime.Now;
|
||||
}
|
||||
else
|
||||
{
|
||||
// GET.
|
||||
request.Method = HttpMethod.Get;
|
||||
responseData.RequestStarted = DateTime.Now;
|
||||
response = await HttpClient.SendAsync(request, requestData.CancellationToken);
|
||||
responseData.RequestCompleted = DateTime.Now;
|
||||
}
|
||||
|
||||
// Extract response data.
|
||||
responseData.Version = response.Version;
|
||||
responseData.Success = response.IsSuccessStatusCode;
|
||||
responseData.StatusCode = response.StatusCode;
|
||||
responseData.ReasonPhrase = response.ReasonPhrase;
|
||||
if (response.Headers != null)
|
||||
{
|
||||
foreach (var header in response.Headers)
|
||||
responseData.ResponseHeaders[header.Key] = new List<String>(header.Value);
|
||||
}
|
||||
if (response.Content != null && response.Content.Headers != null)
|
||||
{
|
||||
foreach (var header in response.Content.Headers)
|
||||
responseData.ContentHeaders[header.Key] = new List<String>(header.Value);
|
||||
}
|
||||
responseData.Data = await response.Content.ReadAsStringAsync();
|
||||
if (response != null) response.Dispose();
|
||||
}
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
// Ignore if task has been cancelled.
|
||||
if (requestData.IsCancellationRequested) throw exc;
|
||||
// Record exception.
|
||||
responseData.Exception = exc;
|
||||
if (exc.InnerException != null)
|
||||
{
|
||||
exc = exc.InnerException;
|
||||
}
|
||||
String msg = exc.GetType().Name + ": " + exc.Message;
|
||||
SaLogger.Warn(fn + " " + msg + ExceptionUtil.GetStackEntries(exc, 6).Concat("", "\n\t"));
|
||||
responseData.ErrorStateMessage = msg;
|
||||
}
|
||||
requestData.ThrowIfCancellationRequested();
|
||||
|
||||
// Complete response data.
|
||||
responseData.Elapsed = DateTime.Now - startedAt;
|
||||
return responseData;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// R e s p o n s e S t a t u s D e s c r i p t i o n
|
||||
//-------------------------------------------------------------------
|
||||
public static String GetResponseStatusDescription (HttpStatusCode httpStatus)
|
||||
{
|
||||
// Ref. https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
|
||||
switch (httpStatus)
|
||||
{
|
||||
// 1xx: Informational - Request received, continuing process.
|
||||
case HttpStatusCode.Continue: return "Continue";
|
||||
// 2xx: Success - The action was successfully received, understood, and accepted.
|
||||
case HttpStatusCode.OK: return "OK";
|
||||
case HttpStatusCode.Created: return "Created";
|
||||
case HttpStatusCode.Accepted: return "Accepted";
|
||||
// 3xx: Redirection - Further action must be taken in order to complete the request.
|
||||
case HttpStatusCode.MultipleChoices: return "Multiple choices";
|
||||
case HttpStatusCode.MovedPermanently: return "Moved permanently";
|
||||
case HttpStatusCode.Found: return "Found";
|
||||
// 4xx: Client Error -The request contains bad syntax or cannot be fulfilled.
|
||||
case HttpStatusCode.BadRequest: return "Bad request";
|
||||
case HttpStatusCode.Unauthorized: return "Unauthorized";
|
||||
case HttpStatusCode.PaymentRequired: return "Payment required";
|
||||
case HttpStatusCode.Forbidden: return "Forbidden";
|
||||
case HttpStatusCode.NotFound: return "Not found";
|
||||
case HttpStatusCode.MethodNotAllowed: return "Method not allowed";
|
||||
case HttpStatusCode.NotAcceptable: return "Not acceptable";
|
||||
// 5xx: Server Error -The server failed to fulfill an apparently valid request.
|
||||
case HttpStatusCode.InternalServerError: return "Internal server error";
|
||||
}
|
||||
return httpStatus.ToString();
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Sarand.Net.Standard.Base.Helpers
|
||||
{
|
||||
//=======================================================================
|
||||
public static class SacsJsonHelper
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
public static dynamic DeserializeJsonObject(String data)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<dynamic>(data);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Sarand.Net.Standard.Base.Helpers
|
||||
{
|
||||
//=======================================================================
|
||||
public static class SacsRandomHelper
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Properties.
|
||||
public static RandomNumberGenerator RandomNumberGenerator { get; } = RandomNumberGenerator.Create();
|
||||
private static Random random;
|
||||
public static Random Random { get { if (random == null) random = new Random(GetInt32(Int32.MaxValue)); return random; } }
|
||||
|
||||
public static void Fill(byte[] byteArray)
|
||||
{
|
||||
// ToDo RandomNumberGenerator.Fill(byteArray);
|
||||
RandomNumberGenerator.GetBytes(byteArray);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Generates a random integer between 0 (inclusive) and a specified exclusive upper
|
||||
/// bound using a cryptographically strong random number generator.
|
||||
/// </summary>
|
||||
/// <param name="toExclusive">The exclusive upper bound of the random range.</param>
|
||||
/// <returns>A random integer between 0 (inclusive) and toExclusive (exclusive).</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">The toExclusive parameter is less than or equal to 0.</exception>
|
||||
public static int GetInt32(int toExclusive)
|
||||
{
|
||||
return RandomNumberGenerator.GetInt32(toExclusive);
|
||||
//return Random.Next(toExclusive);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Generates a random integer between a specified inclusive lower bound and a specified
|
||||
/// exclusive upper bound using a cryptographically strong random number generator.
|
||||
/// </summary>
|
||||
/// <param name="fromInclusive">The inclusive lower bound of the random range.</param>
|
||||
/// <param name="toExclusive">The exclusive upper bound of the random range.</param>
|
||||
/// <returns>A random integer between fromInclusive (inclusive) and toExclusive (exclusive).</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">The toExclusive parameter is less than or equal to the fromInclusive parameter.</exception>
|
||||
public static int GetInt32(int fromInclusive, int toExclusive)
|
||||
{
|
||||
return RandomNumberGenerator.GetInt32(fromInclusive, toExclusive);
|
||||
//return Random.Next(fromInclusive, toExclusive);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace Sarand.Net.Standard.Base.Helpers
|
||||
{
|
||||
//=======================================================================
|
||||
public static class SacsResourceHelper
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Definitions.
|
||||
public enum TextResource
|
||||
{
|
||||
SaBaseCss,
|
||||
SaBaseJs,
|
||||
JQueryJs,
|
||||
JQueryUiCss,
|
||||
JQueryUiJs,
|
||||
}
|
||||
public enum ImageResource
|
||||
{
|
||||
// Burgers.
|
||||
Icon_Burger_Black_48_png,
|
||||
Icon_Burger_Black_64_png,
|
||||
// Arrows.
|
||||
Arrow_Left_Black_16_png,
|
||||
Arrow_Left_Double_Black_16_png,
|
||||
Arrow_Right_Black_16_png,
|
||||
Arrow_Right_Double_Black_16_png,
|
||||
Arrow_Left_Black_24_png,
|
||||
Arrow_Left_Double_Black_24_png,
|
||||
Arrow_Right_Black_24_png,
|
||||
Arrow_Right_Double_Black_24_png,
|
||||
// IconShock.
|
||||
IconShock_Arrow_Down_Purple_72_png,
|
||||
IconShock_Arrow_Right_Purple_72_png,
|
||||
IconShock_Add_Green_72_png,
|
||||
IconShock_Pencil_Orange_72_png,
|
||||
IconShock_Close_Red_72_png,
|
||||
IconShock_TrashCan_Grey_72_png,
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// NB 'Properties' not available in Core .Net project.
|
||||
// Resources must be loaded via csproj so that they are available in the Manifest Resources:
|
||||
// <ItemGroup>
|
||||
// <EmbeddedResource Include="Properties\Resources.resx">
|
||||
// <Generator>ResXFileCodeGenerator</Generator>
|
||||
// <LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
// </EmbeddedResource>
|
||||
// <EmbeddedResource Include="Resources\html\*.css" />
|
||||
// <EmbeddedResource Include="Resources\html\*.js" />
|
||||
// </ItemGroup>
|
||||
// REf. https://stackoverflow.com/questions/38762368/embedded-resource-in-net-core-libraries
|
||||
//-------------------------------------------------------------------
|
||||
public static String GetTextResource(TextResource textResource)
|
||||
{
|
||||
String partialPath = "";
|
||||
String resourceFilename = "";
|
||||
switch (textResource)
|
||||
{
|
||||
case TextResource.SaBaseCss:
|
||||
//return Properties.Resources.sa_base_css;
|
||||
resourceFilename = "sa-base.css";
|
||||
break;
|
||||
case TextResource.SaBaseJs:
|
||||
//return Properties.Resources.sa_base_js;
|
||||
resourceFilename = "sa-base.js";
|
||||
break;
|
||||
case TextResource.JQueryJs:
|
||||
//return Properties.Resources.jquery_1_12_1_min_js;
|
||||
resourceFilename = "jquery-1.12.1.min.js";
|
||||
break;
|
||||
case TextResource.JQueryUiCss:
|
||||
//return Properties.Resources.jquery_ui_1_12_1_min_css;
|
||||
resourceFilename = "jquery-ui-1.12.1.min.css";
|
||||
break;
|
||||
case TextResource.JQueryUiJs:
|
||||
//return Properties.Resources.jquery_ui_1_12_1_min_js;
|
||||
resourceFilename = "jquery-ui-1.12.1.min.js";
|
||||
break;
|
||||
}
|
||||
if (!StringUtil.IsEmpty(resourceFilename))
|
||||
{
|
||||
var assembly = typeof(SacsResourceHelper).GetTypeInfo().Assembly;
|
||||
String resourceText = GetManifestResourceAsText(assembly, partialPath, resourceFilename);
|
||||
if (!StringUtil.IsEmpty(resourceText))
|
||||
return resourceText;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
public static byte[] GetImageResource(ImageResource imageResource)
|
||||
{
|
||||
String partialPath = "";
|
||||
String resourceFilename = "";
|
||||
switch (imageResource)
|
||||
{
|
||||
//-----------------------------------------------------------
|
||||
// Burgers.
|
||||
case ImageResource.Icon_Burger_Black_48_png:
|
||||
resourceFilename = "burger_black_48.png";
|
||||
break;
|
||||
case ImageResource.Icon_Burger_Black_64_png:
|
||||
resourceFilename = "burger_black_64.png";
|
||||
break;
|
||||
//-----------------------------------------------------------
|
||||
// Arrows.
|
||||
case ImageResource.Arrow_Left_Black_16_png:
|
||||
resourceFilename = "arrow_left_black_16.png";
|
||||
break;
|
||||
case ImageResource.Arrow_Left_Double_Black_16_png:
|
||||
resourceFilename = "arrow_left_double_black_16.png";
|
||||
break;
|
||||
case ImageResource.Arrow_Right_Black_16_png:
|
||||
resourceFilename = "arrow_right_black_16.png";
|
||||
break;
|
||||
case ImageResource.Arrow_Right_Double_Black_16_png:
|
||||
resourceFilename = "arrow_right_double_black_16.png";
|
||||
break;
|
||||
case ImageResource.Arrow_Left_Black_24_png:
|
||||
resourceFilename = "arrow_left_black_24.png";
|
||||
break;
|
||||
case ImageResource.Arrow_Left_Double_Black_24_png:
|
||||
resourceFilename = "arrow_left_double_black_24.png";
|
||||
break;
|
||||
case ImageResource.Arrow_Right_Black_24_png:
|
||||
resourceFilename = "arrow_right_black_24.png";
|
||||
break;
|
||||
case ImageResource.Arrow_Right_Double_Black_24_png:
|
||||
resourceFilename = "arrow_right_double_black_24.png";
|
||||
break;
|
||||
//-----------------------------------------------------------
|
||||
// IconShock.
|
||||
case ImageResource.IconShock_Arrow_Down_Purple_72_png:
|
||||
partialPath = "IconShock";
|
||||
resourceFilename = "arrow_down_purple_72.png";
|
||||
break;
|
||||
case ImageResource.IconShock_Arrow_Right_Purple_72_png:
|
||||
partialPath = "IconShock";
|
||||
resourceFilename = "arrow_right_purple_72.png";
|
||||
break;
|
||||
case ImageResource.IconShock_Add_Green_72_png:
|
||||
partialPath = "IconShock";
|
||||
resourceFilename = "add_green_72.png";
|
||||
break;
|
||||
case ImageResource.IconShock_Pencil_Orange_72_png:
|
||||
partialPath = "IconShock";
|
||||
resourceFilename = "pencil_orange_72.png";
|
||||
break;
|
||||
case ImageResource.IconShock_Close_Red_72_png:
|
||||
partialPath = "IconShock";
|
||||
resourceFilename = "close_red_72.png";
|
||||
break;
|
||||
case ImageResource.IconShock_TrashCan_Grey_72_png:
|
||||
partialPath = "IconShock";
|
||||
resourceFilename = "trashcan_grey_72.png";
|
||||
break;
|
||||
//-----------------------------------------------------------
|
||||
}
|
||||
if (!StringUtil.IsEmpty(resourceFilename))
|
||||
{
|
||||
var assembly = typeof(SacsResourceHelper).GetTypeInfo().Assembly;
|
||||
byte[] resourceData = GetManifestResourceAsBinary(assembly, partialPath, resourceFilename);
|
||||
if (resourceData != null)
|
||||
return resourceData;
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>Retrieve resource as String based on assembly and partial resource name.</summary>
|
||||
public static String GetManifestResourceAsText(Assembly assembly, String partialPath, String resourceNameEndsWith)
|
||||
{
|
||||
String fn = typeof(SacsResourceHelper).Name + ".GetManifestResourceAsText";
|
||||
if (StringUtil.IsEmpty(resourceNameEndsWith)) return "";
|
||||
|
||||
using (Stream resourceStream = GetManifestResourceStream(assembly, partialPath, resourceNameEndsWith))
|
||||
{
|
||||
if (resourceStream != null)
|
||||
{
|
||||
using (StreamReader resourceReader = new StreamReader(resourceStream, Encoding.UTF8))
|
||||
{
|
||||
return resourceReader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>Retrieve resource as byte array based on assembly and partial resource name.</summary>
|
||||
public static byte[] GetManifestResourceAsBinary(Assembly assembly, String partialPath, String resourceNameEndsWith)
|
||||
{
|
||||
String fn = typeof(SacsResourceHelper).Name + ".GetManifestResourceAsBinary";
|
||||
if (
|
||||
StringUtil.IsEmpty(resourceNameEndsWith)) return null;
|
||||
using (Stream resourceStream = GetManifestResourceStream(assembly, partialPath, resourceNameEndsWith))
|
||||
{
|
||||
if (resourceStream != null)
|
||||
{
|
||||
using (BinaryReader resourceReader = new BinaryReader(resourceStream, Encoding.UTF8))
|
||||
{
|
||||
return resourceReader.ReadBytes((int)resourceStream.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
SaLogger.Warn(fn + " Failed to locate text resource: " + resourceNameEndsWith);
|
||||
return null;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>Retrieve resource as stream based on assembly and partial resource name.</summary>
|
||||
public static Stream GetManifestResourceStream(Assembly assembly, String partialPath, String resourceNameEndsWith)
|
||||
{
|
||||
String fn = typeof(SacsResourceHelper).Name + ".GetManifestResourceStream";
|
||||
//Logger.Debug(fn + " Manifest resource names: " + new List<String>(assembly.GetManifestResourceNames()).Concat("","\n\t"));
|
||||
foreach (String resourceName in assembly.GetManifestResourceNames())
|
||||
{
|
||||
//SaLogger.Debug(fn + " Resource: " + resourceName);
|
||||
|
||||
// Match the partial path.
|
||||
if (!StringUtil.IsEmpty(partialPath) && !resourceName.ContainsCI(partialPath)) continue;
|
||||
// Match the resource filename to the end of the resource name - resource names start with default namespace (eg. 'Sarand').
|
||||
if (!resourceName.EndsWithCI(resourceNameEndsWith)) continue;
|
||||
|
||||
//SaLogger.Debug(fn + " Resource filename: " + resourceNameEndsWith + ". Matched to resource: " + resourceName);
|
||||
return assembly.GetManifestResourceStream(resourceName);
|
||||
}
|
||||
SaLogger.Warn(fn + " Resource filename not found: " + resourceNameEndsWith);
|
||||
return null;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
Reference in New Issue
Block a user