Saxonite 1.2.11.2

This commit is contained in:
Andy Howard
2024-02-27 15:45:02 +00:00
parent f3350fdc23
commit 2239f37408
54 changed files with 3613 additions and 1175 deletions
+15 -11
View File
@@ -1,16 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<RootNamespace>Sacs.Net.Core</RootNamespace>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>Sacs</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\SacsStandardBase\SacsStandardBase.csproj" />
</ItemGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<Folder Include="Web\App\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SacsStandardBase\SacsStandardBase.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Web\App\" />
</ItemGroup>
</Project>
+423
View File
@@ -0,0 +1,423 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Routing;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Helpers;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
namespace Sacs.Web.Components
{
//=======================================================================
public class SaController : Controller
{
//-------------------------------------------------------------------
/// <summary>
/// Protected construction - use derived objects.
/// </summary>
protected SaController()
{
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
}
base.Dispose(disposing);
}
//-------------------------------------------------------------------
// Properties.
private static String ClassName { get; } = nameof(SaController);
public DateTime CreatedAt { get; } = DateTime.Now;
public bool DebugMode { get; } = SacsSystem.DebugMode;
/// <summary>
/// Indicate whether session data should be stored by action.
/// <para>Any action that might modify the session data must store it.</para>
/// <para>Some actions such as scoring a password's complexity don't need to.</para>
/// </summary>
public virtual bool StoreSessionData { get; protected set; } = true;
//-------------------------------------------------------------------
// Culture.
private CultureInfo sessionCulture;
private CultureInfo RequestCulture { get { return Request.HttpContext.Features.Get<IRequestCultureFeature>().RequestCulture.Culture ?? CultureInfo.InvariantCulture; } }
public CultureInfo SessionCulture
{
get
{
if (sessionCulture == null)
{
sessionCulture = RequestCulture;
// ToDo Adjust culture if needed.
}
return sessionCulture;
}
}
private string LanguageIso2
{
get
{
var feature = HttpContext.Features.Get<IRequestCultureFeature>();
return feature.RequestCulture.UICulture.TwoLetterISOLanguageName.ToLower();
}
}
//-------------------------------------------------------------------
// Redirection locations.
//-------------------------------------------------------------------
public static readonly String ParamName_SessionExpired = "SessionExpired";
public enum RedirectLocation
{
WebAppBase,
WebAppHome,
WebAppHome_SessionExpired,
}
public String GetRedirectLocation(RedirectLocation redirectLocation)
{
string protocol = Request.Scheme;
var parameters = new RouteValueDictionary();
String homeAction = Url.Action("Index", "Home", parameters, protocol);
switch (redirectLocation)
{
case RedirectLocation.WebAppBase:
return Regex.Replace(homeAction, "\\/$", "") + "/";
case RedirectLocation.WebAppHome:
return homeAction;
case RedirectLocation.WebAppHome_SessionExpired:
// Set the session expired parameter so that the page can report it.
// NB Paramater must have a value to be included in the URL query string.
parameters.Add(ParamName_SessionExpired, "1");
return homeAction;
}
return "";
}
//-------------------------------------------------------------------
// Action execution events.
//-------------------------------------------------------------------
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//if (DebugMode) SaLogger.Debug(GetType().Name + ".OnActionExecuting" + (ViewData.Model != null ? " Model: " + ViewData.Model : ""));
base.OnActionExecuting(filterContext);
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
//if (DebugMode) SaLogger.Debug(GetType().Name + ".OnActionExecuted" + (ViewData.Model != null ? " Model: " + ViewData.Model : ""));
base.OnActionExecuted(filterContext);
}
//-------------------------------------------------------------------
public static String EncryptParameters(NameValueCollection parameters, String base64IV, String base64Key)
{
String fn = ClassName + ".EncryptParameters";
try
{
// Copy the list of parameters and sort the keys.
NameValueCollection parametersToEncrypt = new NameValueCollection(parameters);
String[] paramKeys = parametersToEncrypt.AllKeys;
Array.Sort(paramKeys);
// Serialise parameters as if in a URL query string - so that they can be deserialised in DecryptParameters() below.
// Start with a random length parameter name at start of list to obscure the encryption.
string queryString = SacsRandomHelper.getRandomLengthNumber(4) + "&";
queryString += String.Join("&", paramKeys.Select(a => a + "=" + HttpUtility.UrlEncode(parametersToEncrypt[a])));
// Encrypt data.
String encrypted = SacsEncryptionHelper.Encrypt(queryString, base64IV, base64Key);
//if (SacsSystem.DebugMode) SaLogger.Debug(fn + "\n\tData: " + queryString + " \n\tEncrypted: " + encrypted);
return encrypted;
}
catch (Exception exc) { SaLogger.Warn(fn + " " + ExceptionUtil.GetErrorStateMessage(exc)); }
return "";
}
public static NameValueCollection DecryptParameters(String encrypted, String base64IV, String base64Key)
{
String fn = ClassName + ".DecryptParameters";
try
{
String queryString = SacsEncryptionHelper.Decrypt(encrypted, base64IV, base64Key);
//if (SacsSystem.DebugMode) SaLogger.Debug(fn + " \n\tEncrypted: " + encrypted + "\n\tData: " + queryString);
return HttpUtility.ParseQueryString(queryString);
}
catch (Exception exc) { SaLogger.Warn(fn + " " + ExceptionUtil.GetErrorStateMessage(exc)); }
return new NameValueCollection();
}
//-------------------------------------------------------------------
// Cookies.
//-------------------------------------------------------------------
/// <summary>Retrieve the value from the named request cookie.</summary>
public String GetRequestCookie(String key)
{
if (Request.Cookies.ContainsKey(key))
return Request.Cookies[key];
return null;
}
/// <summary>Set the value against the named response cookie.</summary>
public void SetResponseCookie(string key, string value, DateTime? expiresAt = null, bool isEssential = false)
{
CookieOptions options = new CookieOptions();
options.HttpOnly = true;
options.Secure = true;
options.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict;
if (expiresAt.HasValue)
options.Expires = expiresAt;
if (isEssential)
options.IsEssential = true;
Response.Cookies.Append(key, value, options);
}
//-------------------------------------------------------------------
// Ajax actions.
//-------------------------------------------------------------------
protected static readonly Encoding ajaxResponseEncoding = Encoding.UTF8;
protected IActionResult GetAjaxResult_GoHome(AjaxResponse_Base responseData)
{
responseData.SetGoHome();
return Json(responseData);
}
protected IActionResult GetAjaxResult_InvalidSession(AjaxResponse_Base responseData)
{
responseData.SetGoHome();
return Json(responseData);
}
//-------------------------------------------------------------------
// Response data.
// Null values are ignored by JSON serialisation.
// - See Saxonite.Net.Core.Web.Base.App.SaxStartup.ConfigureServices()
// - Ref. config.services.AddMvc(...).AddJsonOptions(options => { options.JsonSerializerOptions.IgnoreNullValues = true; });
//-------------------------------------------------------------------
// Base response data - for simple responses.
public class AjaxResponse_Base
{
// Response status.
public bool? Success { get; set; } = null;
public bool? Error { get; set; } = null;
public bool? SessionExpired { get; protected set; } = null;
public bool? GoHome { get; protected set; } = null;
public AjaxResponse_Base SetSessionExpired(String title, String text)
{
SessionExpired = true;
return SetMessage(title, text);
}
public AjaxResponse_Base SetGoHome()
{
GoHome = true;
return this;
}
public AjaxResponse_Base SetGoHome(String title, String text)
{
GoHome = true;
return SetMessage(title, text);
}
// Message - displays alert popup.
public class MessageData
{
public String Title { get; set; }
public String Text { get; set; }
}
public MessageData Message { get; private set; }
public AjaxResponse_Base SetMessage(String title, String text)
{
if (!StringUtil.IsEmpty(text))
Message = new MessageData() { Title = title, Text = text };
return this;
}
}
//-------------------------------------------------------------------
public class AjaxResponse_Data : AjaxResponse_Base
{
//---------------------------------------------------------------
// Construction.
protected AjaxResponse_Data(SaModel model)
{
}
//---------------------------------------------------------------
/// <summary>Item data - Common properties for data items.</summary>
public class ItemData_Base
{
// Common item properties.
public String Id { get; set; }
public String Notes { get; set; }
public String StatusCode { get; set; }
public String StatusDesc { get; set; }
// Flags for common status values.
// Return true or null so that property is not serialised when false.
public bool? Awaiting { get { return StatusCode.EqualsCI(SacsSystem.GetStatusCode(SaStatus.Awaiting)) ? true : (bool?)null; } }
public bool? Active { get { return StatusCode.EqualsCI(SacsSystem.GetStatusCode(SaStatus.Active)) ? true : (bool?)null; } }
public bool? Inactive { get { return StatusCode.EqualsCI(SacsSystem.GetStatusCode(SaStatus.Inactive)) ? true : (bool?)null; } }
public bool? Deleted { get { return StatusCode.EqualsCI(SacsSystem.GetStatusCode(SaStatus.Deleted)) ? true : (bool?)null; } }
/// <summary>Created on date - YYYYMMDD</summary>
public String CreatedOn { get; set; }
/// <summary>Created on date - formatted per locale</summary>
public String CreatedOnFmt { get; set; }
/// <summary>Updated on date - YYYYMMDD</summary>
public String UpdatedOn { get; set; }
/// <summary>Updated on date - formatted per locale</summary>
public String UpdatedOnFmt { get; set; }
}
/// <summary>Contact Method properties.</summary>
public class ContactMethodData
{
public String Method { get; set; }
public String MethodCaption { get; set; }
public String Value { get; set; }
}
/// <summary>Address properties.</summary>
public class AddressData : ItemData_Base
{
public String Line1 { get; set; }
public String Line2 { get; set; }
public String Line3 { get; set; }
public String Town { get; set; }
public String RegionIdProvince { get; set; }
public String RegionNameProvince { get; set; }
public String RegionIdCountry { get; set; }
public String RegionNameCountry { get; set; }
public String PostalCode { get; set; }
}
/// <summary>
/// Item data with label and value.
/// </summary>
public class ItemLabelValue
{
public String Label { get; set; }
public String Value { get; set; }
}
/// <summary>
/// Item data with just Id and name.
/// <para>- Id is String to allow for obfuscated values.</para>
/// </summary>
public class ItemIdName
{
public String Id { get; set; }
public String Name { get; set; }
}
/// <summary>Item code and desc values.</summary>
public class ItemCodeDesc
{
public String Code { get; set; }
public String Desc { get; set; }
}
//---------------------------------------------------------------
// Form data.
public class FormControlValue
{
public FormControlValue(String controlName, String value, String error)
{
ControlName = controlName;
Value = value;
Error = error;
}
public String ControlName { get; }
public String Value { get; set; }
public String Error { get; set; }
}
private readonly Dictionary<String, FormControlValue> controlValues = new Dictionary<String, FormControlValue>();
public List<FormControlValue> ControlValues { get { return controlValues.Count > 0 ? controlValues.Values.ToList() : null; } }
public void SetControlValue(String controlName, String value, String error = null)
{
if (controlValues.ContainsKey(controlName))
{
// Modify existing entry in the list.
FormControlValue controlValue = controlValues[controlName];
if (value != null) controlValue.Value = value;
if (error != null) controlValue.Error = error;
}
else
{
// Add new control to list.
controlValues.Add(controlName, new FormControlValue(controlName, value, error));
}
}
/// <summary>Assign the error to the control by name and return this response data.</summary>
public AjaxResponse_Data SetControlError(String controlName, String error)
{
return SetControlError(controlName, error, this);
}
/// <summary>
/// Assign the error to the control by name and return the specified value.
/// <para>- Allows alternative return values to be specified (eg. 'false') to avoid separate return statement in calling routine.</para>
/// </summary>
public T SetControlError<T>(String controlName, String error, T returnValue)
{
SetControlValue(controlName, null, error);
return returnValue;
}
public bool HasControlErrors()
{
foreach (FormControlValue controlValue in controlValues.Values)
{
if (!StringUtil.IsEmpty(controlValue.Error)) return true;
}
return false;
}
//---------------------------------------------------------------
// Notification - briefly displays after response received.
public class NotificationData
{
public String Title { get; set; }
public String Message { get; set; }
}
public NotificationData Notification { get; private set; }
public AjaxResponse_Data SetNotification(String title, String message)
{
Notification = new NotificationData() { Title = title, Message = message };
return this;
}
//---------------------------------------------------------------
// Paged data.
public class PagingData
{
public PagingData(SaModel model) :
this(model.Paging.PageNo, model.Paging.PageLength, model.Paging.ItemCount, model.Paging.ItemTotal)
{ }
public PagingData(uint pageNo, ushort pageLength, ushort itemCount, ulong itemTotal)
{
PageNo = pageNo;
PageLength = pageLength;
ItemCount = itemCount;
ItemTotal = itemTotal;
}
public bool Paged { get { return (PageNo > 0 && PageLength > 0 && ItemTotal > PageLength); } }
public uint PageNo { get; } = 0;
public ushort PageLength { get; } = 0;
public uint PageCount { get { return (PageLength > 0 ? (uint)Math.Ceiling((decimal)ItemTotal / PageLength) : 0); } }
public ushort ItemCount { get; } = 0;
public ulong ItemTotal { get; } = 0;
}
public PagingData Paging { get; private set; }
public void SetPaging(SaModel model)
{
Paging = new PagingData(model);
}
//---------------------------------------------------------------
}
//-------------------------------------------------------------------
}
//=======================================================================
}
+150
View File
@@ -0,0 +1,150 @@
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Dbas;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static Sarand.Net.Standard.Base.Bus.SaErrorStateItem;
namespace Sacs.Web.Components
{
//=======================================================================
public class SaModel
{
//-------------------------------------------------------------------
// Data members.
private readonly List<String> infoMessages = new List<string>();
private readonly List<String> warningMessages = new List<string>();
private readonly List<String> errorMessages = new List<string>();
//-------------------------------------------------------------------
/// <summary>
/// Protected construction - use derived objects.
/// </summary>
protected SaModel()
{
}
//---------------------------------------------------------------
// Properties.
public bool DebugMode { get; } = SacsSystem.DebugMode;
public bool OK { get { return !HasMessages(); } }
public virtual bool ProhibitCache { get; set; } = true;
public String PageCaption { get; set; }
public bool IncludeGoogleAnalytics { get; set; } = false;
//-------------------------------------------------------------------
// Page configuration.
// Views can be registered to avoid multiple inclusions in a page.
private HashSet<String> registeredViews = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Views can be registered to avoid multiple inclusions in a page.
/// <para>Only generate view content if this method returns true.</para>
/// </summary>
public bool RegisterView(String viewName)
{
#if DEBUG
if (registeredViews.Contains(viewName))
SaLogger.Warn(this.GetType().Name + ".RegisterView View already registered: " + viewName);
#endif
return registeredViews.Add(viewName);
}
//-------------------------------------------------------------------
// Version parameter for includes.
public String HrefVersion
{
get
{
DateTime versionDate = SacsSystem.AssemblyBuildAt;
if (!DateUtil.IsValid(versionDate) || SacsSystem.DebugMode)
versionDate = DateTime.Now;
return DateUtil.Format(versionDate, "yyyyMMdd" /*+ (SacsSystem.Debug ? "HHmm" : "")*/);
}
}
// Additional includes.
public HashSet<String> AdditionalFiles_CSS { get; set; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
public HashSet<String> AdditionalFiles_JS { get; set; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
//-------------------------------------------------------------------
// Redirection locations.
public class RedirectLocationData
{
// Properties populated in Controller_Base.OnActionExecuted.
public String Base { get; set; }
public String Home { get; set; }
public String Home_SessionExpired { get; set; }
}
public RedirectLocationData RedirectLocation { get; } = new RedirectLocationData();
//-------------------------------------------------------------------
// Paging properties.
public class DataPaging
{
private DbQuery.Paging paging { get; } = new DbQuery.Paging();
public void PopulatePagingTo(DbQuery.Paging resultsPaging)
{
resultsPaging.PageLength = paging.PageLength;
resultsPaging.PageNo = paging.PageNo;
}
public bool IsPaged { get { return paging.Paged; } }
public uint PageNo { get { return paging.PageNo; } set { paging.PageNo = value; } }
public String PageNoValue { get { return paging.PageNo.ToString(); } set { paging.PageNo = (uint)StringUtil.ToInt(value); } }
public ushort PageLength { get { return paging.PageLength; } set { paging.PageLength = value; } }
public String PageLengthValue { get { return paging.PageLength.ToString(); } set { paging.PageLength = (ushort)StringUtil.ToShort(value); } }
public uint PageCount { get { return (PageLength > 0 ? (uint)Math.Ceiling((decimal)ItemTotal / paging.PageLength) : 0); } }
public ushort ItemCount { get; set; }
private ulong itemTotal = 0;
public ulong ItemTotal
{
get { return itemTotal; }
set
{
itemTotal = value;
// Check whether the total number of items is less than the current page and reduce page no if needed.
if (IsPaged && (PageNo - 1) * PageLength > itemTotal)
PageNo = (uint)((itemTotal - 1) / PageLength + 1);
}
}
}
public DataPaging Paging { get; } = new DataPaging();
public void PopulatePagingTo(DbQuery.Paging resultsPaging)
{
Paging.PopulatePagingTo(resultsPaging);
}
//-------------------------------------------------------------------
// Messages.
//-------------------------------------------------------------------
public bool HasMessages()
{
return infoMessages.Count > 0 || warningMessages.Count > 0 || errorMessages.Count > 0;
}
public List<String> InfoMessages { get { return new List<string>(infoMessages); } }
public List<String> WarningMessages { get { return new List<string>(warningMessages); } }
public List<String> ErrorMessages { get { return new List<string>(errorMessages); } }
//-------------------------------------------------------------------
// Form messages.
public void AddInfoMessage(String message)
{
if (StringUtil.IsEmpty(StringUtil.TrimAll(message))) return;
infoMessages.Add(message);
}
public void AddWarningMessage(String message)
{
if (StringUtil.IsEmpty(StringUtil.TrimAll(message))) return;
warningMessages.Add(message);
}
public void AddErrorMessage(String message)
{
if (StringUtil.IsEmpty(StringUtil.TrimAll(message))) return;
errorMessages.Add(message);
}
//---------------------------------------------------------------
}
//=======================================================================
}