v1.3.0 Restructure Saxonite workspace and upgrade to .NET 10 (#1)

Move Saxonite’s projects into a dedicated `Workspace` folder and align the solution with .NET 10 and the current Common workspace. Update encryption integration, test project structure and coverage, and repository guidance and upgrade notes.

- Relocate application and library projects while retaining Git rename history.
- Split tests into Admin, Online, and Shared projects; add representative behavioral and serialization tests.
- Update Common integration, application version, and repository documentation.

Application version: 1.3.0

Reviewed-on: #1
Co-authored-by: andy <andy@sarand.com>
This commit was merged in pull request #1.
This commit is contained in:
2026-10-16 18:24:16 +01:00
committed by andy
parent 2c3fb146ab
commit 9f22b522df
1996 changed files with 1992 additions and 1179 deletions
@@ -0,0 +1,607 @@
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Dbas;
using Sarand.Net.Standard.Base.Utils;
using Sarand.Net.Standard.Dbas.Data;
using Saxonite.Base.App;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using Saxonite.Net.Standard.Base.Consoles;
using Saxonite.Net.Standard.Dbas.Mgr;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Saxonite.Net.Core.Web.Base.Consoles
{
//=======================================================================
public abstract class SaxActivityConsole_Web : SaxActivityConsole
{
//-------------------------------------------------------------------
// Data members.
private readonly String alias_ActivityIndex = SaxActivityMgr_Dbas.aliasActivityIndex;
private readonly String alias_ActivityIndex_UserActivity = SaxActivityMgr_Dbas.aliasActivityIndex_UserActivity;
protected class IndexStatsData
{
public int EntriesInserted { get; set; }
public int EntriesUpdated { get; set; }
public int EntriesDeleted { get; set; }
}
//-------------------------------------------------------------------
// Protected construction.
protected SaxActivityConsole_Web(SaxSessionContext sessionContext) :
base(sessionContext)
{
}
//-------------------------------------------------------------------
// Query Activity Index Entries for User Context.
//-------------------------------------------------------------------
private class ActivityData_Web : IndexResults.ActivityData
{
public ActivityData_Web(ItemId itemId, DateTime effectiveOn) : base(itemId, effectiveOn) { }
public void SetOrganisation(SysId organisationId, String organisation, bool hasLogo) { OrganisationId = organisationId; Organisation = organisation; OrganisationHasLogo = hasLogo; }
public void SetAuthor(SysId userAccountId, String author) { UserAccountIdAuthor = userAccountId; Author = author; }
public void SetDetails(String caption, String blurb) { Caption = caption; Blurb = blurb; }
public void SetSport(SysId sportId, String sportName) { SportId = sportId; SportName = sportName; }
public void SetRegion(SysId regionId, String regionName) { RegionId = regionId; RegionName = regionName; }
// User Activity.
public void SetLastListedAt(DateTime lastListedAt) { LastListedAt = lastListedAt; }
public void SetLastViewedAt(DateTime lastViewedAt) { LastViewedAt = lastViewedAt; }
public void SetReaction(Sax.ActivityReaction reaction) { Reaction = reaction; }
public void SetLastReactedAt(DateTime lastReactedAt) { LastReactedAt = lastReactedAt; }
public static int CompareActivityIndices(ActivityData_Web item1, ActivityData_Web item2)
{
int effectiveCompare = item1.EffectiveOn.CompareTo(item2.EffectiveOn);
if (effectiveCompare != 0) return effectiveCompare * -1;
return 1;
}
}
public override IndexResults QueryIndex(IndexContext context)
{
String fn = GetType().Name + ".QueryIndex";
ClearErrorState();
IndexResults results = new IndexResults();
if (!context.IsValid()) return results;
// Columns to query.
ColumnDfn colItemType = Internals.ActivityMgr.GetActivityIndexColumnDfn(SaxActivityIndex.Column.ItemType);
ColumnDfn colItemId = Internals.ActivityMgr.GetActivityIndexColumnDfn(SaxActivityIndex.Column.ItemId);
// NB Would like to include the Organisation namd and logo count but this involves joing to Organisation and item image
// which will make the following query significantly more complex.
ColumnDfn colOrganisationId = Internals.ActivityMgr.GetActivityIndexColumnDfn(SaxActivityIndex.Column.OrganisationIdOwner);
ColumnDfn colOrganisationName = Internals.ActivityMgr.GetActivityIndexColumnDfn(SaxActivityIndex.Column.OrganisationOwner_Name);
ColumnDfn colOrganisationLogoCount = Internals.ActivityMgr.GetActivityIndexColumnDfn(SaxActivityIndex.Column.OrganisationOwner_LogoCount);
String colDataNameEffectiveOn = "EffectiveOn";
// Query the Activity Index.
// NB An item may have multiple index entries but these wil only differe by Sport and/or Region.
// User, Organisation Effective & Expiry dates wil be the same for all entries for an item.
ISacsDatabase db = SaxGlobals.App.GetDatabase(SessionContext, Sax.Database.Core);
DbQuery query = new DbQuery(db.DbmsType);
query.Tables.Add(SaxDbDfn_Core.GetTableName(SaxDbDfn_Core.TableIdx.ActivityIndex, false), alias_ActivityIndex);
query.Columns.Add(SaxDbDfn_Core.GetColumnNameQual(SaxDbDfn_Core.ColumnIdx.ActivityIndex_ItemType, alias_ActivityIndex), colItemType.DataName);
query.Columns.Add(SaxDbDfn_Core.GetColumnNameQual(SaxDbDfn_Core.ColumnIdx.ActivityIndex_ItemId, alias_ActivityIndex), colItemId.DataName);
query.Columns.Add("Max(" + SaxDbDfn_Core.GetColumnNameQual(SaxDbDfn_Core.ColumnIdx.ActivityIndex_EffectiveOn, alias_ActivityIndex) + ")", colDataNameEffectiveOn);
query.GroupBy.Add(SaxDbDfn_Core.GetColumnNameQual(SaxDbDfn_Core.ColumnIdx.ActivityIndex_ItemType, alias_ActivityIndex));
query.GroupBy.Add(SaxDbDfn_Core.GetColumnNameQual(SaxDbDfn_Core.ColumnIdx.ActivityIndex_ItemId, alias_ActivityIndex));
query.OrderBy.Add("Max(" + SaxDbDfn_Core.GetColumnNameQual(SaxDbDfn_Core.ColumnIdx.ActivityIndex_EffectiveOn, alias_ActivityIndex) + ") DESC");
// Filter.
SaxActivityIndex.Params prmActivityIndex = new SaxActivityIndex.Params();
prmActivityIndex.UserAccountIdsOwnerIncluded.AddRange(context.UserAccountIdsOwner);
prmActivityIndex.OrganisationIdsOwnerIncluded.AddRange(context.OrganisationIdsOwner);
prmActivityIndex.SportRegionIdsIncluded.AddRange(context.SportRegionIdsIncluded);
prmActivityIndex.EffectiveFrom = context.EffectiveFrom;
prmActivityIndex.EffectiveTo = context.EffectiveTo;
prmActivityIndex.NotExpiredOn = context.NotExpiredOn;
prmActivityIndex.Alias_ActivityIndex = alias_ActivityIndex;
prmActivityIndex.ResultsPaging.PageNo = 1;
prmActivityIndex.ResultsPaging.PageLength = 100;
query.Filters.AddRange(Internals.ActivityMgr.GetActivityIndexQueryFilter(prmActivityIndex));
// First pass, note the activity Item and effective date.
List<ActivityData_Web> activityDataList = new List<ActivityData_Web>();
using (DbResultSet dbResultSet = DbResultSet_DataReader.ExecuteQuery(db, query.BuildQuery(context.Paging)))
{
while (dbResultSet != null && dbResultSet.NextRecord())
{
NamedValue.Map values = dbResultSet.GetRecordValues();
Sax.DataItemType itemType = (Sax.DataItemType)values.GetLong(colItemType.DataName);
ItemId itemId = new ItemId((short)itemType, new SysId(values.GetLong(colItemId.DataName)));
DateTime effectiveOn = values.GetDate(colDataNameEffectiveOn);
ActivityData_Web activityData = new ActivityData_Web(itemId, effectiveOn);
activityDataList.Add(activityData);
SaLogger.Info(fn + " Activity found - " + itemType.ToString() + ":" + SysId.ToString(itemId.Id) + ". Effective: " + DateUtil.Format(effectiveOn, "d-MMM-yyyy"));
}
}
// Sort the activity list.
activityDataList.Sort(ActivityData_Web.CompareActivityIndices);
// Populate activity details.
PopulateActivityDetails(context, activityDataList);
// ToDo Remove activities with blank caption.
results.Items.AddRange(activityDataList);
return results;
}
//-------------------------------------------------------------------
// Populate Activity Details.
//-------------------------------------------------------------------
private void PopulateActivityDetails(IndexContext context, List<ActivityData_Web> activities)
{
PopulateUserActivityDetails(context, activities);
PopulateArticleDetails(context, activities);
PopulateOrganisationEventDetails(context, activities);
PopulateOrganisationOpportunityDetails(context, activities);
}
//-------------------------------------------------------------------
private void PopulateUserActivityDetails(IndexContext context, List<ActivityData_Web> activities)
{
// Validate parameters.
if (!SysId.IsValid(context.UserAccountId)) return;
// Extract article Ids.
List<ItemId> activityItemIds = activities.Select(item => new ItemId(item.ItemId.Type, item.ItemId.Id)).ToList();
if (activityItemIds.IsEmpty()) return;
// User Activity columns - limit to essential information.
ColumnDfn colItemType = Internals.ActivityMgr.GetUserActivityColumnDfn(SaxUserActivity.Column.ItemType);
ColumnDfn colItemId = Internals.ActivityMgr.GetUserActivityColumnDfn(SaxUserActivity.Column.ItemId);
ColumnDfn colUserAccountId = Internals.ActivityMgr.GetUserActivityColumnDfn(SaxUserActivity.Column.UserAccountId);
ColumnDfn colLastListedAt = Internals.ActivityMgr.GetUserActivityColumnDfn(SaxUserActivity.Column.LastListedAt);
ColumnDfn colLastViewedAt = Internals.ActivityMgr.GetUserActivityColumnDfn(SaxUserActivity.Column.LastViewedAt);
ColumnDfn colReaction = Internals.ActivityMgr.GetUserActivityColumnDfn(SaxUserActivity.Column.Reaction);
ColumnDfn colLastReactedAt = Internals.ActivityMgr.GetUserActivityColumnDfn(SaxUserActivity.Column.LastReactedAt);
List<ColumnDfn> columns = new List<ColumnDfn>() { colItemType, colItemId, colUserAccountId, colLastListedAt, colLastViewedAt, colReaction, colLastReactedAt };
// User Activity filter.
SaxUserActivity.Params prmUserActivity = new SaxUserActivity.Params();
prmUserActivity.UserAccountIdsIncluded.Add(context.UserAccountId);
prmUserActivity.ItemIdsIncluded.AddRange(activityItemIds);
prmUserActivity.StatusCat = SaxUserActivity.StatusCat.ActiveOnly;
using (DbResultSet dbResultSet = Internals.ActivityMgr.ExecuteUserActivityQuery(columns, prmUserActivity))
{
while (dbResultSet != null && dbResultSet.NextRecord())
{
NamedValue.Map values = dbResultSet.GetRecordValues();
ItemId activityItemId = new ItemId((short)values.GetLong(colItemType.DataName), new SysId(values.GetLong(colItemId.DataName)));
DateTime lastListedAt = values.GetDateTime(colLastListedAt.DataName);
DateTime lastViewedAt = values.GetDateTime(colLastViewedAt.DataName);
Sax.ActivityReaction reaction = (Sax.ActivityReaction)values.GetLong(colReaction.DataName);
DateTime lastReactedAt = values.GetDateTime(colLastReactedAt.DataName);
// Populate to corresponding activities.
foreach (ActivityData_Web activity in activities.FindAll(item => item.ItemId.Equals(activityItemId)))
{
activity.SetLastListedAt(lastListedAt);
activity.SetLastViewedAt(lastViewedAt);
activity.SetReaction(reaction);
activity.SetLastReactedAt(lastReactedAt);
}
}
}
}
//-------------------------------------------------------------------
// Article details.
private void PopulateArticleDetails(IndexContext context, List<ActivityData_Web> activities)
{
// Extract article Ids.
List<SysId> articleIds = activities.FindAll(item => item.ItemId.Type == (int)Sax.DataItemType.Article).Select(ItemId => ItemId.ItemId.Id).ToList();
if (articleIds.IsEmpty()) return;
// Article columns - limit to essential information.
ColumnDfn colId = Internals.ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Id);
ColumnDfn colOrganisationId = Internals.ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.OrganisationId);
ColumnDfn colOrganisation = Internals.ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Organisation_Name);
ColumnDfn colOrganisationLogoCount = Internals.ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Organisation_LogoCount);
ColumnDfn colAuthorId = Internals.ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.UserAccountIdAuthor);
ColumnDfn colAuthor = Internals.ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Author_DisplayName);
ColumnDfn colHeadline = Internals.ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Headline);
ColumnDfn colSummary = Internals.ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Summary);
ColumnDfn colItemImageIdThumbnail = Internals.ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.ItemImageIdThumbnail);
ColumnDfn colSportId = Internals.ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.SportIdPrimary);
ColumnDfn colSportName = Internals.ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Sport_Name);
ColumnDfn colRegionId = Internals.ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.RegionIdPrimary);
ColumnDfn colRegionName = Internals.ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Region_Name);
List<ColumnDfn> columns = new List<ColumnDfn>()
{
colId, colOrganisationId, colOrganisation, colOrganisationLogoCount, colAuthor, colAuthorId,
colHeadline, colSummary, colItemImageIdThumbnail, colSportId, colSportName, colRegionId, colRegionName
};
// Article filter.
SaxArticle.Params prmArticle = new SaxArticle.Params();
prmArticle.IdsIncluded.AddRange(articleIds);
prmArticle.StatusCat = SaxArticle.StatusCat.ActiveOnly;
using (DbResultSet dbResultSet = Internals.ActivityMgr.ExecuteArticleQuery(columns, prmArticle))
{
while (dbResultSet != null && dbResultSet.NextRecord())
{
NamedValue.Map values = dbResultSet.GetRecordValues();
ItemId itemId = new ItemId((short)Sax.DataItemType.Article, new SysId(values.GetLong(colId.DataName)));
SysId organisationId = new SysId(values.GetLong(colOrganisationId.DataName));
String organisation = values.GetString(colOrganisation.DataName);
long organisationLogoCount = values.GetLong(colOrganisationLogoCount.DataName);
SysId authorId = new SysId(values.GetLong(colAuthorId.DataName));
String author = Internals.ActivityMgr.GetArticleDataValue(colAuthor, values).ToString();
String headline = values.GetString(colHeadline.DataName);
String summary = values.GetString(colSummary.DataName);
SysId itemImageIdThumbnail = new SysId(values.GetLong(colItemImageIdThumbnail.DataName));
SysId sportId = new SysId(values.GetLong(colSportId.DataName));
String sportName = values.GetString(colSportName.DataName);
SysId regionId = new SysId(values.GetLong(colRegionId.DataName));
String regionName = values.GetString(colRegionName.DataName);
// Populate to corresponding activities.
foreach (ActivityData_Web activity in activities.FindAll(item => item.ItemId.Equals(itemId)))
{
activity.SetOrganisation(organisationId, organisation, organisationLogoCount > 0);
activity.SetAuthor(authorId, author);
activity.SetDetails(headline, summary);
activity.ItemImageIdThumbnail = itemImageIdThumbnail;
activity.SetSport(sportId, sportName);
activity.SetRegion(regionId, regionName);
}
}
}
}
//-------------------------------------------------------------------
// Organisation Event details.
private void PopulateOrganisationEventDetails(IndexContext context, List<ActivityData_Web> activities)
{
// Extract organisationEvent Ids.
List<SysId> organisationEventIds = activities.FindAll(item => item.ItemId.Type == (int)Sax.DataItemType.OrganisationEvent).Select(ItemId => ItemId.ItemId.Id).ToList();
if (organisationEventIds.IsEmpty()) return;
// Event columns - limit to essential information.
ColumnDfn colId = Internals.ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.Id);
ColumnDfn colOrganisationId = Internals.ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.OrganisationId);
ColumnDfn colOrganisation = Internals.ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.Organisation_Name);
ColumnDfn colOrganisationLogoCount = Internals.ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.Organisation_LogoCount);
ColumnDfn colEventAt = Internals.ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.EventAt_DateTime);
ColumnDfn colSummary = Internals.ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.Summary);
ColumnDfn colDetails = Internals.ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.Details);
ColumnDfn colSportId = Internals.ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.SportIdPrimary);
ColumnDfn colSportName = Internals.ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.Sport_Name);
ColumnDfn colRegionId = Internals.ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.RegionIdCountry);
ColumnDfn colRegionName = Internals.ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.Region_Name);
List<ColumnDfn> columns = new List<ColumnDfn>()
{
colId, colOrganisationId, colOrganisation, colOrganisationLogoCount,
colEventAt, colSummary, colDetails, colSportId, colSportName, colRegionId, colRegionName
};
// Event filter.
SaxOrganisationEvent.Params prmEvent = new SaxOrganisationEvent.Params();
prmEvent.IdsIncluded.AddRange(organisationEventIds);
prmEvent.StatusCat = SaxOrganisationEvent.StatusCat.ActiveOnly;
using (DbResultSet dbResultSet = Internals.ActivityMgr.ExecuteOrganisationEventQuery(columns, prmEvent))
{
while (dbResultSet != null && dbResultSet.NextRecord())
{
NamedValue.Map values = dbResultSet.GetRecordValues();
ItemId itemId = new ItemId((short)Sax.DataItemType.OrganisationEvent, new SysId(values.GetLong(colId.DataName)));
SysId organisationId = new SysId(values.GetLong(colOrganisationId.DataName));
String organisation = values.GetString(colOrganisation.DataName);
long organisationLogoCount = values.GetLong(colOrganisationLogoCount.DataName);
DateTime eventAt = values.GetDateTime(colEventAt.DataName);
String eventAtDisplay = DateUtil.Format(eventAt, "d-MMM-yy");
if (eventAt.Hour > 0 || eventAt.Minute > 0)
eventAtDisplay += DateUtil.Format(values.GetDateTime(colEventAt.DataName), " HH:mm");
String eventSummary = values.GetString(colSummary.DataName);
String eventDetails = values.GetString(colDetails.DataName);
SysId sportId = new SysId(values.GetLong(colSportId.DataName));
String sportName = values.GetString(colSportName.DataName);
SysId regionId = new SysId(values.GetLong(colRegionId.DataName));
String regionName = values.GetString(colRegionName.DataName);
// Populate to corresponding activities.
foreach (ActivityData_Web activity in activities.FindAll(item => item.ItemId.Equals(itemId)))
{
activity.SetOrganisation(organisationId, organisation, organisationLogoCount > 0);
activity.SetDetails(eventAtDisplay, eventSummary);
activity.SetSport(sportId, sportName);
activity.SetRegion(regionId, regionName);
}
}
}
}
//-------------------------------------------------------------------
// Organisation Opportunity details.
private void PopulateOrganisationOpportunityDetails(IndexContext context, List<ActivityData_Web> activities)
{
// Extract opportunity Ids.
List<SysId> opportunityIds = activities.FindAll(item => item.ItemId.Type == (int)Sax.DataItemType.OrganisationOpportunity).Select(ItemId => ItemId.ItemId.Id).ToList();
if (opportunityIds.IsEmpty()) return;
// Opportunity columns - limit to essential information.
ColumnDfn colId = Internals.ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.Id);
ColumnDfn colOrganisationId = Internals.ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.OrganisationId);
ColumnDfn colOrganisation = Internals.ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.Organisation_Name);
ColumnDfn colOrganisationLogoCount = Internals.ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.Organisation_LogoCount);
ColumnDfn colPosition = Internals.ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.Position);
ColumnDfn colOutline = Internals.ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.Outline);
ColumnDfn colSportId = Internals.ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.SportIdPrimary);
ColumnDfn colSportName = Internals.ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.Sport_Name);
ColumnDfn colRegionId = Internals.ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.RegionIdPrimary);
ColumnDfn colRegionName = Internals.ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.Region_Name);
List<ColumnDfn> columns = new List<ColumnDfn>()
{
colId, colOrganisationId, colOrganisation, colOrganisationLogoCount,
colPosition, colOutline, colSportId, colSportName, colRegionId, colRegionName
};
// Opportunity filter.
SaxOrganisationOpportunity.Params prmOpportunity = new SaxOrganisationOpportunity.Params();
prmOpportunity.IdsIncluded.AddRange(opportunityIds);
prmOpportunity.StatusCat = SaxOrganisationOpportunity.StatusCat.ActiveOnly;
using (DbResultSet dbResultSet = Internals.ActivityMgr.ExecuteOrganisationOpportunityQuery(columns, prmOpportunity))
{
while (dbResultSet != null && dbResultSet.NextRecord())
{
NamedValue.Map values = dbResultSet.GetRecordValues();
ItemId itemId = new ItemId((short)Sax.DataItemType.OrganisationOpportunity, new SysId(values.GetLong(colId.DataName)));
SysId organisationId = new SysId(values.GetLong(colOrganisationId.DataName));
String organisation = values.GetString(colOrganisation.DataName);
long organisationLogoCount = values.GetLong(colOrganisationLogoCount.DataName);
String position = values.GetString(colPosition.DataName);
String outline = values.GetString(colOutline.DataName);
SysId sportId = new SysId(values.GetLong(colSportId.DataName));
String sportName = values.GetString(colSportName.DataName);
SysId regionId = new SysId(values.GetLong(colRegionId.DataName));
String regionName = values.GetString(colRegionName.DataName);
// Populate to corresponding activities.
foreach (ActivityData_Web activity in activities.FindAll(item => item.ItemId.Equals(itemId)))
{
activity.SetOrganisation(organisationId, organisation, organisationLogoCount > 0);
activity.SetDetails(position, outline);
activity.SetSport(sportId, sportName);
activity.SetRegion(regionId, regionName);
}
}
}
}
//-------------------------------------------------------------------
// Store Activity Index.
//-------------------------------------------------------------------
protected override bool StoreActivityIndexData(IndexData indexData, DbUpdateInfo dbUpdateInfo)
{
// Store the Activity Index data for the specified item - Article, Event, Opportunity etc.
// An item may have multiple index entries to describe the different
// combination of sport and region that they apply to.
// NB All entries for the same item must have the same values for:
// - Owner User Account Id.
// - Owner Organisation Id.
// - Effective on date.
// - Expires on date.
String fn = GetType().Name + ".StoreActivityIndexData";
ClearErrorState();
// Validate paramaters.
if (!ItemId.IsValid(indexData.ItemId)) return false;
if (!ValidActivityItemTypes.Contains((Sax.DataItemType)indexData.ItemId.Type))
{
SaLogger.Warn(fn + " Invalid item type specified for Activity Index: " + ((Sax.DataItemType)indexData.ItemId.Type).ToString());
return SetErrorState("Invalid item type specified for Activity Index");
}
IndexStatsData stats = new IndexStatsData();
// Index entries required for activity.
List<IndexEntryData> indexEntryDataList = GetEntriesForIndex(indexData);
// Retrieve existing index entries for the item.
List<SaxActivityIndex> indexEntries = Internals.ActivityMgr.GetActivityIndexListForItem(indexData.ItemId);
foreach (SaxActivityIndex indexEntry in indexEntries)
{
// Is the index entry still required.
// Logic only compares the Ids (item, sport, region) and then updates the effective date if a match is found.
// Based on the fact that and change in the effective date will impact all entries in the index for the item
// and this approach minimises the number of updates required as Ids can't be changed in code (protected setters).
IndexEntryData indexEntrydata = indexEntryDataList.Find(item => item.MatchesIds(indexEntry));
if (indexEntrydata != null)
{
// Match found - update properties.
indexEntry.UserAccountIdOwner = indexEntrydata.UserAccountIdOwner;
indexEntry.OrganisationIdOwner = indexEntrydata.OrganisationIdOwner;
indexEntry.EffectiveOn = indexEntrydata.EffectiveOn;
indexEntry.ExpiresOn = indexEntrydata.ExpiresOn;
if (indexEntry.Changed)
{
if (Internals.ActivityMgr.UpdateActivityIndex(indexEntry, dbUpdateInfo))
stats.EntriesUpdated++;
else SaLogger.Warn(fn + " Failed to update Activity Index - " + Internals.ActivityMgr.ErrorStateMessage);
}
// Remove the entry from the 'required' list.
// Entries remaining in this list will be created.
indexEntryDataList.Remove(indexEntrydata);
}
else
{
// Index entry no longer required.
if (Internals.ActivityMgr.DeleteActivityIndex(indexEntry, dbUpdateInfo))
stats.EntriesDeleted++;
else SaLogger.Warn(fn + " Failed to delete Activity Index - " + Internals.ActivityMgr.ErrorStateMessage);
}
}
// The remaining required Index entries will be created.
foreach (IndexEntryData indexEntry in indexEntryDataList)
{
SaxActivityIndex itemIndexEntry = Internals.ActivityMgr.CreateActivityIndex(indexEntry.ItemId, indexEntry.SportId, indexEntry.RegionId);
itemIndexEntry.UserAccountIdOwner = indexEntry.UserAccountIdOwner;
itemIndexEntry.OrganisationIdOwner = indexEntry.OrganisationIdOwner;
itemIndexEntry.EffectiveOn = indexEntry.EffectiveOn;
itemIndexEntry.ExpiresOn = indexEntry.ExpiresOn;
if (Internals.ActivityMgr.InsertActivityIndex(itemIndexEntry, dbUpdateInfo))
stats.EntriesInserted++;
else SaLogger.Warn(fn + " Failed to insert Activity Index - " + Internals.ActivityMgr.ErrorStateMessage);
}
// Log statistics.
List<String> statsLog = new List<string>();
if (stats.EntriesInserted > 0) statsLog.Add("Inserted: " + stats.EntriesInserted);
if (stats.EntriesUpdated > 0) statsLog.Add("Updated: " + stats.EntriesUpdated);
if (stats.EntriesDeleted > 0) statsLog.Add("Deleted: " + stats.EntriesDeleted);
SaLogger.Info(fn + " Activity Index stored for " + ((Sax.DataItemType)indexData.ItemId.Type).ToString() + ":" + indexData.ItemId.Id.ToLong() + "." + statsLog.Concat("", "", ". "));
return true;
}
//-------------------------------------------------------------------
protected override bool ClearActivityIndex(ItemId itemId, DbUpdateInfo dbUpdateInfo)
{
String fn = GetType().Name + ".ClearActivityIndex";
ClearErrorState();
String tableName = SaxDbDfn_Core.GetTableName(SaxDbDfn_Core.TableIdx.ActivityIndex);
String filter =
SaxDbDfn_Core.GetColumnName(SaxDbDfn_Core.ColumnIdx.ActivityIndex_ItemType) + "=" + itemId.Type +
" And " + SaxDbDfn_Core.GetColumnName(SaxDbDfn_Core.ColumnIdx.ActivityIndex_ItemId) + "=" + itemId.Id.ToLong();
ISacsDatabase db = CoreDatabase;
int recordsDeleted = db.ExecuteDelete(tableName, filter);
if (recordsDeleted > 0)
SaLogger.Info(fn + " Records deleted: " + recordsDeleted + ". Filter: " + filter, true);
return true;
}
//-------------------------------------------------------------------
// User Activities
//-------------------------------------------------------------------
/// <summary>
/// Record listing of activities by user - those populated to the application page.
/// <para>- Activities not previously listed are added to the user's list.</para>
/// <para>- Activities previously listed are updated in the user's list.</para>
/// <para>- User Activities no longer listed are flagged as 'Not currently listed'.</para>
/// </summary>
public override bool RecordUserActivitiesListed(SysId userAccountId, List<ItemId> activityItemIds, DbUpdateInfo dbUpdateInfo)
{
String fn = GetType().Name + ".RecordUserActivitiesListed";
if (!SysId.IsValid(userAccountId)) return SetErrorState("User Account must be specified");
if (activityItemIds.IsEmpty()) return SetErrorState("Activity item ids must be specified");
String sqlNow = "'" + DateUtil.FormatSqlDateTime(DateTime.Now) + "'";
String sqlStatus = "'" + SacsSystem.GetStatusCode(SaxUserActivity.DefaultStatus) + "'";
// Merge query - insert missing user activities and update existing.
List<String> query = new List<String>()
{
"Merge",
"\tInto " + SaxDbDfn_Core.GetTableName(SaxDbDfn_Core.TableIdx.UserActivity) + " As UsrAct",
// Virtual table defining Activity Item Ids currently listed.
"\tUsing",
"\t(",
"\t\tSelect ItemType, ItemId From (Select 0 ItemType, 0 ItemId" + activityItemIds.Select(item => " Union Select " + item.Type.ToString() + ", " + item.Id.ToString()).ToList().Concat("", " ") + ") Dat Where Dat.ItemType>0 And Dat.ItemId>0",
"\t) As Act",
"\tOn UsrAct.UserAccountId="+userAccountId.ToString() + " And UsrAct.ItemType=Act.ItemType And UsrAct.ItemId=Act.ItemId",
// Matched - Update the User Activity to indicate 'Currently listed' and update the last listed date/time.
"\tWhen Matched Then",
"\t\tUpdate Set CurrentlyListed=1, ListedAtLast=" + sqlNow + ", Status=" + sqlStatus + ", UpdateOn=" + sqlNow + ", UpdateBy=" + dbUpdateInfo.UserId.ToString() + ", UpdateHost='" + dbUpdateInfo.Host + "'",
// Not Matched By Target - Insert the User Activity and indicate 'Currently listed' with first and last listed date/times.
"\tWhen Not Matched Then",
"\t\tInsert",
"\t\t\t(UserAccountId, ItemType, ItemId, CurrentlyListed, ListedAtFirst, ListedAtLast, Reaction, UserActivityData, Notes, Flags, Status, CreateOn, CreateBy, CreateHost, UpdateOn, UpdateBy, UpdateHost)",
"\t\t\tValues ("+userAccountId.ToString()+", Act.ItemType, Act.ItemId, 1, " + sqlNow + ", " + sqlNow + ", 0, '', '', 0, " + sqlStatus + ", " + sqlNow + ", " + dbUpdateInfo.UserId.ToString() + ", '" + dbUpdateInfo.Host + "', " + sqlNow + ", " + dbUpdateInfo.UserId.ToString() + ", '" + dbUpdateInfo.Host + "')",
// Not Matched By Source - Update the User Activity to indicate 'Not currently listed'.
"\tWhen Not Matched By Source And UsrAct.UserAccountId=" + userAccountId.ToString() + " And CurrentlyListed=1 Then",
"\t\tUpdate Set CurrentlyListed=0, UpdateOn=" + sqlNow + ", UpdateBy=" + dbUpdateInfo.UserId.ToString() + ", UpdateHost='" + dbUpdateInfo.Host + "'",
";"
};
String sql = query.Concat("\n");
//SaLogger.Debug(fn + " Query:\n" + sql);
ISacsDatabase db = CoreDatabase;
int rows = db.ExecuteNonQuery(sql);
if (db.HasErrorState())
SaLogger.Debug(fn + " Database error: " + db.ErrorStateMessage);
SaLogger.Debug(fn + " Rows modified: " + rows);
return true;
}
//-------------------------------------------------------------------
public override bool RecordUserActivityViewed(SysId userAccountId, ItemId activityItemId, DbUpdateInfo dbUpdateInfo)
{
String fn = GetType().Name + ".RecordUserActivityViewed";
if (!SysId.IsValid(userAccountId)) return SetErrorState("User Account must be specified");
if (!ItemId.IsValid(activityItemId)) return SetErrorState("Activity item id must be specified");
DateTime now = DateTime.Now;
SaxUserActivity userActivity = Internals.ActivityMgr.GetUserActivityForUserItem(userAccountId, activityItemId);
if (userActivity != null)
{
// Update activity.
userActivity.CurrentlyListed = true;
if (!DateUtil.IsValid(userActivity.ViewedAtFirst))
userActivity.ViewedAtFirst = now;
userActivity.ViewedAtLast = now;
userActivity.Status = SaxUserActivity.DefaultStatus;
if (!Internals.ActivityMgr.UpdateUserActivity(userActivity, dbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to update User Activity for viewing: " + Internals.ActivityMgr.ErrorStateMessage);
return SetErrorState(Internals.ActivityMgr.ErrorStateMessage);
}
}
else
{
SaLogger.Warn(fn + " User Activity not found. UserAccountId: " + userAccountId.ToString() + ". Item Type: " + (Sax.DataItemType)activityItemId.Type + ". ItemId: " + activityItemId.Id.ToString() + ".");
return SetErrorState("User Activity not found");
}
return true;
}
//-------------------------------------------------------------------
public override bool RecordUserActivityReaction(SysId userAccountId, ItemId activityItemId, Sax.ActivityReaction activityReaction, DbUpdateInfo dbUpdateInfo)
{
String fn = GetType().Name + ".RecordUserActivityReaction";
if (!SysId.IsValid(userAccountId)) return SetErrorState("User Account must be specified");
if (!ItemId.IsValid(activityItemId)) return SetErrorState("Activity item id must be specified");
DateTime now = DateTime.Now;
SaxUserActivity userActivity = Internals.ActivityMgr.GetUserActivityForUserItem(userAccountId, activityItemId);
if (userActivity != null)
{
if (userActivity.Reaction != activityReaction)
{
// Update activity.
userActivity.CurrentlyListed = true;
if (!DateUtil.IsValid(userActivity.ViewedAtFirst))
userActivity.ViewedAtFirst = now;
userActivity.ViewedAtLast = now;
userActivity.Reaction = activityReaction;
if (!DateUtil.IsValid(userActivity.ReactedAtFirst))
userActivity.ReactedAtFirst = now;
userActivity.ReactedAtLast = now;
userActivity.Status = SaxUserActivity.DefaultStatus;
if (!Internals.ActivityMgr.UpdateUserActivity(userActivity, dbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to update User Activity with reaction: " + Internals.ActivityMgr.ErrorStateMessage);
return SetErrorState(Internals.ActivityMgr.ErrorStateMessage);
}
}
}
else
{
SaLogger.Warn(fn + " User Activity not found. UserAccountId: " + userAccountId.ToString() + ". Item Type: " + (Sax.DataItemType)activityItemId.Type + ". ItemId: " + activityItemId.Id.ToString() + ".");
return SetErrorState("User Activity not found");
}
return true;
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,24 @@
using Saxonite.Net.Standard.Base.Consoles;
using System;
using System.Collections.Generic;
using System.Text;
namespace Saxonite.Net.Core.Web.Base.Consoles
{
//=======================================================================
public abstract class SaxConsoleHelper_Web : SaxConsoleHelper
{
//-------------------------------------------------------------------
// Protected construction.
protected SaxConsoleHelper_Web()
{
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,291 @@
using Microsoft.IdentityModel.Tokens;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Utils;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using Saxonite.Net.Standard.Base.Consoles;
using System;
using System.Collections.Generic;
using System.Text;
namespace Saxonite.Net.Core.Web.Base.Consoles
{
//=======================================================================
public abstract class SaxDataCacheConsole_Web : SaxDataCacheConsole
{
//-------------------------------------------------------------------
// Data members.
private class DataMembers
{
public SaxInternals internals;
public Dictionary<Sax.DataItemType, Dictionary<SysId, String>> encryptedIdCache = new Dictionary<Sax.DataItemType, Dictionary<SysId, String>>();
}
private DataMembers data = new DataMembers();
//-------------------------------------------------------------------
// Protected construction.
protected SaxDataCacheConsole_Web(SaxSessionContext sessionContext) :
base(sessionContext)
{
data.internals = new SaxInternals(sessionContext, false);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
data.internals.Dispose();
}
base.Dispose(disposing);
}
//-------------------------------------------------------------------
// Utilities.
protected abstract void ForceRefresh();
public override void PopulateCachedDataToInternals(SaxInternals internals)
{
if (regionsData.regionDataMap == null) ForceRefresh();
// Regions.
List<SaxRegion.Data> activeRegionDataList = new List<SaxRegion.Data>();
lock (regionsData)
activeRegionDataList.AddRange(regionsData.activeRegionDataList);
internals.PopulateRegionCache(activeRegionDataList);
// Sports.
List<SaxSport.Data> activeSportDataList = new List<SaxSport.Data>();
lock (sportsData)
activeSportDataList.AddRange(sportsData.activeSportDataList);
internals.PopulateSportCache(activeSportDataList);
}
/// <summary>
/// Encrypted Ids are stored in cache to ensure that they are consistent throughout a user's session
/// by persisting them through the life of the cache (application restart).
/// <para>Required as the encrypted values are compared in javascript.</para>
/// <para>Candidate encrypted value supplied by calling routine as cache doesn't know how to encrypt - used if Id not previously encrypted.</para>
/// </summary>
public override String GetEncryptedId(Sax.DataItemType itemType, SysId id, String candidateValue)
{
// Invalid Id always has a blank encrypted value.
if (!SysId.IsValid(id)) return "";
// Only Regions and Sports are supported.
if (!(new List<Sax.DataItemType>() { Sax.DataItemType.Sport, Sax.DataItemType.Region }).Contains(itemType))
return candidateValue;
lock (data.encryptedIdCache)
{
Dictionary<SysId, String> itemIds = data.encryptedIdCache.GetValue(itemType, null);
if (itemIds == null)
{
// Add cache entry for this item type.
itemIds = new Dictionary<SysId, string>();
data.encryptedIdCache.Add(itemType, itemIds);
}
String encryptedId = itemIds.GetValue(id, null);
if (encryptedId == null && !StringUtil.IsEmpty(candidateValue))
{
// Use the candidate value and store it.
encryptedId = candidateValue;
itemIds.Add(id, encryptedId);
}
return encryptedId;
}
}
//-------------------------------------------------------------------
// Regions.
public bool RegionsSupported { get; protected set; } = false;
private class RegionsData
{
public Dictionary<SysId, SaxRegion.Data> regionDataMap = null;
public List<SaxRegion.Data> activeRegionDataList = null;
}
private readonly RegionsData regionsData = new RegionsData();
public override List<SaxRegion.Data> GetAllRegions()
{
String fn = GetType().Name + ".ActiveRegions";
if (!RegionsSupported) throw new NotSupportedException(fn + " Regions not supported.");
List<SaxRegion.Data> activeRegions = new List<SaxRegion.Data>();
if (regionsData.regionDataMap == null) ForceRefresh();
lock (regionsData)
{
if (regionsData.regionDataMap == null) throw new NotSupportedException(fn + " Regions not populated.");
activeRegions.AddRange(regionsData.regionDataMap.Values);
}
return activeRegions;
}
public override List<SaxRegion.Data> GetActiveRegions()
{
String fn = GetType().Name + ".ActiveRegions";
if (!RegionsSupported) throw new NotSupportedException(fn + " Regions not supported.");
List<SaxRegion.Data> activeRegions = new List<SaxRegion.Data>();
if (regionsData.regionDataMap == null) ForceRefresh();
lock (regionsData)
{
if (regionsData.regionDataMap == null) throw new NotSupportedException(fn + " Regions not populated.");
activeRegions.AddRange(regionsData.activeRegionDataList);
}
return activeRegions;
}
protected void RefreshRegions()
{
String fn = GetType().Name + ".RefreshRegions";
if (!RegionsSupported) throw new NotSupportedException(fn + " Regions not supported.");
try
{
DateTime startedAt = DateTime.Now;
// Refreshed lists will be populated in isolation and then used to instantaneously replace the cache lists.
Dictionary<SysId, SaxRegion.Data> newRegionDataMap = new Dictionary<SysId, SaxRegion.Data>();
List<SaxRegion.Data> newActiveRegionDataList = new List<SaxRegion.Data>();
// Query Regions to replace cache.
SaxRegion.Params prmRegion = new SaxRegion.Params();
prmRegion.StatusCat = SaxRegion.StatusCat.NotDeleted;
foreach (SaxRegion region in data.internals.ReferenceMgr.GetRegionList(prmRegion))
{
// Only interested in retaining the Data objects.
newRegionDataMap[region.Id] = region.GetData();
region.Dispose();
}
// Populate replacement list of active regions.
foreach (SaxRegion.Data regionData in newRegionDataMap.GetValues())
{
if (IsRegionActive(newRegionDataMap, regionData))
newActiveRegionDataList.Add(regionData.Clone());
}
// Replace the list of Regions.
lock (regionsData)
{
// Assign the refreshed regions and active regions.
// NB The old regions don't need to be disposed as they are SaxRegion.Data objects.
regionsData.regionDataMap = newRegionDataMap;
regionsData.activeRegionDataList = newActiveRegionDataList;
}
SaLogger.Info(fn + " Regions found: " + regionsData.regionDataMap.Count + ". Elapsed: " + DateUtil.FormatNumberOfSeconds((DateTime.Now - startedAt).TotalSeconds));
}
catch (Exception exc)
{
SaLogger.Error(fn + ExceptionUtil.GetLogMessage(exc, "refreshing Regions cache"));
}
}
private static bool IsRegionActive(Dictionary<SysId, SaxRegion.Data> regionDataMap, SaxRegion.Data regionData)
{
if (regionData == null) return false;
if (!regionData.IsStatusActive) return false;
if (SysId.IsValid(regionData.RegionIdParent))
return IsRegionActive(regionDataMap, regionDataMap.GetValue(regionData.RegionIdParent, null));
return true;
}
private SaxRegion GetRegion(SaxRegion.Data regionData)
{
if (regionData == null) return null;
SaxRegion region = data.internals.ReferenceMgr.CreateRegion();
region.SetData(regionData);
return region;
}
//-------------------------------------------------------------------
// Sports.
public bool SportsSupported { get; protected set; } = false;
private class SportsData
{
public Dictionary<SysId, SaxSport.Data> sportDataMap = null;
public List<SaxSport.Data> activeSportDataList = null;
}
private readonly SportsData sportsData = new SportsData();
public override List<SaxSport.Data> GetAllSports()
{
String fn = GetType().Name + ".GetAllSports";
if (!SportsSupported) throw new NotSupportedException(fn + " Sports not supported.");
List<SaxSport.Data> sports = new List<SaxSport.Data>();
if (sportsData.sportDataMap == null) ForceRefresh();
lock (sportsData)
{
if (sportsData.sportDataMap == null) throw new NotSupportedException(fn + " Sports not populated.");
sports.AddRange(sportsData.sportDataMap.Values);
}
return sports;
}
public override List<SaxSport.Data> GetActiveSports()
{
String fn = GetType().Name + ".GetActiveSports";
if (!SportsSupported) throw new NotSupportedException(fn + " Sports not supported.");
List<SaxSport.Data> activeSports = new List<SaxSport.Data>();
if (sportsData.sportDataMap == null) ForceRefresh();
lock (sportsData)
{
if (sportsData.sportDataMap == null) throw new NotSupportedException(fn + " Sports not populated.");
activeSports.AddRange(sportsData.activeSportDataList);
}
return activeSports;
}
protected void RefreshSports()
{
String fn = GetType().Name + ".RefreshSports";
if (!SportsSupported) throw new NotSupportedException(fn + " Sports not supported.");
try
{
DateTime startedAt = DateTime.Now;
// Refreshed lists will be populated in isolation and then used to instantaneously replace the cache lists.
Dictionary<SysId, SaxSport.Data> newSportDataMap = new Dictionary<SysId, SaxSport.Data>();
List<SaxSport.Data> newActiveSportDataList = new List<SaxSport.Data>();
// Query Sports to replace cache.
SaxSport.Params prmSport = new SaxSport.Params();
prmSport.StatusCat = SaxSport.StatusCat.NotDeleted;
foreach (SaxSport sport in data.internals.ReferenceMgr.GetSportList(prmSport))
{
// Only interested in retaining the Data objects.
newSportDataMap[sport.Id] = sport.GetData();
sport.Dispose();
}
// Populate replacement list of active sports.
foreach (SaxSport.Data sportData in newSportDataMap.GetValues())
{
if (IsSportActive(newSportDataMap, sportData))
newActiveSportDataList.Add(sportData.Clone());
}
// Replace the list of Sports.
lock (sportsData)
{
// Assign the refreshed sports and active sports.
// NB The old sports don't need to be disposed as they are SaxSport.Data objects.
sportsData.sportDataMap = newSportDataMap;
sportsData.activeSportDataList = newActiveSportDataList;
}
SaLogger.Info(fn + " Sports found: " + sportsData.sportDataMap.Count + ". Elapsed: " + DateUtil.FormatNumberOfSeconds((DateTime.Now - startedAt).TotalSeconds));
}
catch (Exception exc)
{
SaLogger.Error(fn + ExceptionUtil.GetLogMessage(exc, "refreshing Sports cache"));
}
}
private static bool IsSportActive(Dictionary<SysId, SaxSport.Data> sportDataMap, SaxSport.Data sportData)
{
if (sportData == null) return false;
if (!sportData.IsStatusActive) return false;
return true;
}
private SaxSport GetSport(SaxSport.Data sportData)
{
if (sportData == null) return null;
SaxSport sport = data.internals.ReferenceMgr.CreateSport();
sport.SetData(sportData);
return sport;
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,21 @@
using Saxonite.Net.Standard.Base.Consoles;
using System;
using System.Collections.Generic;
using System.Text;
namespace Saxonite.Net.Core.Web.Base.Consoles
{
//=======================================================================
public class SaxEmailConsole_Web : SaxEmailConsole
{
//-------------------------------------------------------------------
// Protected construction.
public SaxEmailConsole_Web(SaxEmailConfig emailConfig) :
base(emailConfig)
{
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,98 @@
using Microsoft.Extensions.Hosting;
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.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Base.Consoles
{
//=======================================================================
public abstract class SaxHostedServiceConsole_Web : SacsDisposable, IHostedService
{
//-------------------------------------------------------------------
// Definitions.
//-------------------------------------------------------------------
// Data members.
private Timer processTimer;
//-------------------------------------------------------------------
// Construction.
protected SaxHostedServiceConsole_Web ()
{
}
//-------------------------------------------------------------------
// Clean-up.
protected override void Dispose(bool disposing)
{
DisposeTimer();
if (ProcessTaskData != null) ProcessTaskData.Dispose();
ProcessTaskData = null;
}
private void DisposeTimer()
{
if (processTimer != null) processTimer.Dispose();
processTimer = null;
}
//-------------------------------------------------------------------
// Properties.
// Task data - allows cancellation to be triggered for executing processes, eg. when application is stopping.
protected SacsTaskData ProcessTaskData { get; private set; }
//-------------------------------------------------------------------
/// <summary>
/// Periodic processing to execute application processes as required.
/// </summary>
protected abstract void DoWork(object state);
//-------------------------------------------------------------------
/// <summary>
/// Triggered when the application host is ready to start the service.
/// </summary>
/// <param name="cancellationToken">Indicates that the start process has been aborted.</param>
/// <returns></returns>
public virtual Task StartAsync(CancellationToken cancellationToken)
{
SaLogger.Info($"{SysUtil.Fn} Timed Background Service started.");
// Initialise the background task cancellation token.
ProcessTaskData = new SacsTaskData();
// Initialise the scheduled task timer.
processTimer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromMinutes(1));
return Task.CompletedTask;
}
/// <summary>
/// Triggered when the application host is performing a graceful shutdown.
/// </summary>
/// <param name="cancellationToken">Indicates that the shutdown process should no longer be graceful.</param>
/// <returns></returns>
public virtual Task StopAsync(CancellationToken cancellationToken)
{
SaLogger.Info($"{SysUtil.Fn} Timed Background Service stopped.");
// Signal cancellation to the background tasks.
ProcessTaskData.TriggerCancellation();
// Stop the timer.
processTimer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,29 @@
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Consoles;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Saxonite.Net.Core.Web.Base.Consoles
{
//=======================================================================
public abstract class SaxSessionDataConsole_Web : SaxSessionDataConsole
{
//-------------------------------------------------------------------
// Definitions.
//-------------------------------------------------------------------
// Data members.
//-------------------------------------------------------------------
// Construction.
protected SaxSessionDataConsole_Web(SaxSessionContext sessionContext) :
base(sessionContext)
{
}
//-------------------------------------------------------------------
//-------------------------------------------------------------------
}
//=======================================================================
}