590 lines
24 KiB
C#
590 lines
24 KiB
C#
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.AspNetCore.JsonPatch;
|
|
using Newtonsoft.Json.Linq;
|
|
using EnumsNET;
|
|
using AyaNova.Util;
|
|
using AyaNova.Api.ControllerHelpers;
|
|
using AyaNova.Models;
|
|
|
|
namespace AyaNova.Biz
|
|
{
|
|
|
|
internal class WidgetBiz : BizObject, IJobObject
|
|
{
|
|
|
|
internal WidgetBiz(AyContext dbcontext, long currentUserId, long userLocaleId, AuthorizationRoles UserRoles)
|
|
{
|
|
ct = dbcontext;
|
|
UserId = currentUserId;
|
|
UserLocaleId = userLocaleId;
|
|
CurrentUserRoles = UserRoles;
|
|
BizType = AyaType.Widget;
|
|
}
|
|
|
|
internal static WidgetBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext)
|
|
{
|
|
return new WidgetBiz(ct, UserIdFromContext.Id(httpContext.Items), UserLocaleIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items));
|
|
}
|
|
|
|
//Version for internal use
|
|
internal static WidgetBiz GetBizInternal(AyContext ct)
|
|
{
|
|
return new WidgetBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_LANGUAGE_ID, AuthorizationRoles.BizAdminFull);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//EXISTS
|
|
internal async Task<bool> ExistsAsync(long id)
|
|
{
|
|
return await ct.Widget.AnyAsync(e => e.Id == id);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
/// GET
|
|
///
|
|
///
|
|
|
|
//Get without logging
|
|
internal async Task<Widget> GetNoLogAsync(long fetchId)
|
|
{
|
|
//This is simple so nothing more here, but often will be copying to a different output object or some other ops
|
|
return await ct.Widget.SingleOrDefaultAsync(m => m.Id == fetchId);
|
|
}
|
|
|
|
//Get one with logging
|
|
internal async Task<Widget> GetAsync(long fetchId)
|
|
{
|
|
//This is simple so nothing more here, but often will be copying to a different output object or some other ops
|
|
var ret = await ct.Widget.SingleOrDefaultAsync(m => m.Id == fetchId);
|
|
if (ret != null)
|
|
{
|
|
//Log
|
|
EventLogProcessor.LogEventToDatabase(new Event(UserId, fetchId, BizType, AyaEvent.Retrieved), ct);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//CREATE
|
|
|
|
|
|
//route linked version for external api access
|
|
internal async Task<Widget> CreateAsync(Widget inObj)
|
|
{
|
|
Validate(inObj, null);
|
|
if (HasErrors)
|
|
return null;
|
|
else
|
|
{
|
|
//do stuff with widget
|
|
Widget outObj = inObj;
|
|
|
|
//Test get serial id visible id number from generator
|
|
outObj.Serial = ServerBootConfig.WIDGET_SERIAL.GetNext();
|
|
|
|
outObj.Tags = TagUtil.NormalizeTags(outObj.Tags);
|
|
outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields);
|
|
|
|
await ct.Widget.AddAsync(outObj);
|
|
await ct.SaveChangesAsync();
|
|
|
|
//Handle child and associated items:
|
|
EventLogProcessor.LogEventToDatabase(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct);
|
|
SearchIndex(outObj, true);
|
|
TagUtil.ProcessUpdateTagsInRepository(ct, outObj.Tags, null);
|
|
return outObj;
|
|
}
|
|
}
|
|
|
|
|
|
//Internal version for seeding
|
|
internal Widget Create(AyContext TempContext, Widget inObj)
|
|
{
|
|
Validate(inObj, null);
|
|
if (HasErrors)
|
|
return null;
|
|
else
|
|
{
|
|
//do stuff with widget
|
|
Widget outObj = inObj;
|
|
|
|
//Test get serial id visible id number from generator
|
|
outObj.Serial = ServerBootConfig.WIDGET_SERIAL.GetNext();
|
|
outObj.Tags = TagUtil.NormalizeTags(outObj.Tags);
|
|
outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields);
|
|
|
|
TempContext.Widget.Add(outObj);
|
|
TempContext.SaveChanges();
|
|
|
|
//Handle child and associated items:
|
|
EventLogProcessor.LogEventToDatabase(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), TempContext);
|
|
SearchIndex(outObj, true);
|
|
TagUtil.ProcessUpdateTagsInRepository(TempContext, outObj.Tags, null);
|
|
|
|
return outObj;
|
|
}
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//DUPLICATE
|
|
//
|
|
|
|
internal async Task<Widget> DuplicateAsync(Widget dbObj)
|
|
{
|
|
|
|
Widget outObj = new Widget();
|
|
CopyObject.Copy(dbObj, outObj);
|
|
outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255);
|
|
outObj.Id = 0;
|
|
outObj.ConcurrencyToken = 0;
|
|
|
|
//Test get serial id visible id number from generator
|
|
outObj.Serial = ServerBootConfig.WIDGET_SERIAL.GetNext();
|
|
|
|
await ct.Widget.AddAsync(outObj);
|
|
await ct.SaveChangesAsync();
|
|
|
|
//Handle child and associated items:
|
|
EventLogProcessor.LogEventToDatabase(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct);
|
|
SearchIndex(outObj, true);
|
|
TagUtil.ProcessUpdateTagsInRepository(ct, outObj.Tags, null);
|
|
return outObj;
|
|
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//UPDATE
|
|
//
|
|
|
|
//put
|
|
internal bool Put(Widget dbObj, Widget inObj)
|
|
{
|
|
|
|
//make a snapshot of the original for validation but update the original to preserve workflow
|
|
Widget SnapshotOfOriginalDBObj = new Widget();
|
|
CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj);
|
|
|
|
//Replace the db object with the PUT object
|
|
CopyObject.Copy(inObj, dbObj, "Id,Serial");
|
|
|
|
dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags);
|
|
dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields);
|
|
|
|
//Set "original" value of concurrency token to input token
|
|
//this will allow EF to check it out
|
|
ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = inObj.ConcurrencyToken;
|
|
|
|
|
|
Validate(dbObj, SnapshotOfOriginalDBObj);
|
|
if (HasErrors)
|
|
return false;
|
|
|
|
//Associated items
|
|
EventLogProcessor.LogEventToDatabase(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct);
|
|
SearchIndex(dbObj, false);
|
|
TagUtil.ProcessUpdateTagsInRepository(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags);
|
|
|
|
return true;
|
|
}
|
|
|
|
//patch
|
|
internal bool Patch(Widget dbObj, JsonPatchDocument<Widget> objectPatch, uint concurrencyToken)
|
|
{
|
|
//Validate Patch is allowed
|
|
//Note: Id and Serial are all checked for and disallowed in the validate code by default
|
|
if (!ValidateJsonPatch<Widget>.Validate(this, objectPatch)) return false;
|
|
|
|
//make a snapshot of the original for validation but update the original to preserve workflow
|
|
Widget SnapshotOfOriginalDBObj = new Widget();
|
|
CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj);
|
|
|
|
//Do the patching
|
|
objectPatch.ApplyTo(dbObj);
|
|
|
|
dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags);
|
|
dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields);
|
|
|
|
ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = concurrencyToken;
|
|
Validate(dbObj, SnapshotOfOriginalDBObj);
|
|
if (HasErrors)
|
|
return false;
|
|
|
|
//Associated items
|
|
EventLogProcessor.LogEventToDatabase(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct);
|
|
SearchIndex(dbObj, false);
|
|
|
|
TagUtil.ProcessUpdateTagsInRepository(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
private void SearchIndex(Widget obj, bool isNew)
|
|
{
|
|
//SEARCH INDEXING
|
|
var SearchParams = new Search.SearchIndexProcessObjectParameters(UserLocaleId, obj.Id, BizType, obj.Name);
|
|
SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Serial).AddText(obj.Tags).AddCustomFields(obj.CustomFields);
|
|
|
|
|
|
if (isNew)
|
|
Search.ProcessNewObjectKeywords(SearchParams);
|
|
else
|
|
Search.ProcessUpdatedObjectKeywords(SearchParams);
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//DELETE
|
|
//
|
|
internal bool Delete(Widget dbObj)
|
|
{
|
|
//Determine if the object can be deleted, do the deletion tentatively
|
|
//Probably also in here deal with tags and associated search text etc
|
|
|
|
ValidateCanDelete(dbObj);
|
|
if (HasErrors)
|
|
return false;
|
|
ct.Widget.Remove(dbObj);
|
|
ct.SaveChanges();
|
|
|
|
//Associated items
|
|
EventLogProcessor.DeleteObject(UserId, BizType, dbObj.Id, dbObj.Name, ct);
|
|
ct.SaveChanges();
|
|
Search.ProcessDeletedObjectKeywords(dbObj.Id, BizType);
|
|
TagUtil.ProcessDeleteTagsInRepository(ct, dbObj.Tags);
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// LISTS
|
|
//
|
|
|
|
|
|
|
|
|
|
//get many (paged)
|
|
internal async Task<ApiPagedResponse> GetList(IUrlHelper Url, string routeName, ListOptions listOptions)
|
|
{
|
|
listOptions.Offset = listOptions.Offset ?? ListOptions.DefaultOffset;
|
|
listOptions.Limit = listOptions.Limit ?? ListOptions.DefaultLimit;
|
|
|
|
//BUILD THE QUERY
|
|
//base query
|
|
var q = "SELECT *, xmin FROM AWIDGET ";
|
|
|
|
//GET THE FILTER / SORT
|
|
if (listOptions.DataFilterId > 0)
|
|
{
|
|
var TheFilter = await ct.DataFilter.FirstOrDefaultAsync(x => x.Id == listOptions.DataFilterId);
|
|
|
|
//BUILD WHERE AND APPEND IT
|
|
q = q + FilterSqlCriteriaBuilder.DataFilterToSQLCriteria(TheFilter, ObjectFields.ObjectFieldsList(ObjectFields.WIDGET_KEY), UserId);
|
|
|
|
//BUILD ORDER BY AND APPEND IT
|
|
q = q + FilterSqlOrderByBuilder.DataFilterToSQLOrderBy(TheFilter);
|
|
}
|
|
else
|
|
{
|
|
//GET DEFAULT ORDER BY
|
|
q = q + FilterSqlOrderByBuilder.DefaultGetManyOrderBy();
|
|
}
|
|
|
|
|
|
#pragma warning disable EF1000
|
|
|
|
//GET THE FULL LIST OF ITEMS
|
|
var items = await ct.Widget
|
|
.FromSqlRaw(q)
|
|
.AsNoTracking()
|
|
.Skip(listOptions.Offset.Value)
|
|
.Take(listOptions.Limit.Value)
|
|
.ToArrayAsync();
|
|
//GET THE RECORD COUNT
|
|
var totalRecordCount = await ct.Widget
|
|
.FromSqlRaw(q)
|
|
.AsNoTracking()
|
|
.CountAsync();
|
|
#pragma warning restore EF1000
|
|
|
|
//BUILD THE PAGING LINKS PORTION
|
|
var pageLinks = new PaginationLinkBuilder(Url, routeName, null, listOptions, totalRecordCount).PagingLinksObject();
|
|
|
|
|
|
//BUILD THE RETURN BASED ON TEMPLATE and MINI CONDITIONAL FORMAT
|
|
//TODO: Get template (MOCKED FOR NOW UNTIL PROOF OF CONCEPT)
|
|
var MOCK_WIDGET_DISPLAY_TEMPLATE_JSON = @"
|
|
{
|
|
""full"":[""WidgetName"",""WidgetSerial"",""WidgetDollarAmount"",""WidgetRoles"",""WidgetStartDate"",""Active""],
|
|
""mini"":[""WidgetName"",""WidgetSerial""]
|
|
}
|
|
";
|
|
|
|
//TODO: BUILD THE COLUMNS RETURN PROPERTY JSON FRAGMENT
|
|
//for MINI format we just use a static column definition built in to this list object
|
|
//for FULL we build it from the template automatically
|
|
//eg: columns:{[ {name:"lt_client_name",datatype:text,ayatype:client},{name:"lt_client_notes",datatype:text},{name:"lt_last_workorder",datatype:number,ayatype:workorder}]}
|
|
//if FULL Pass the template to the ObjectFields which will build the return JSON for here and will also ensure the fields are correct, if any are unknown it will just ignore them but maybe log it?
|
|
string ColumnsJSON = string.Empty;
|
|
if (listOptions.Mini)
|
|
{
|
|
//all mini lists will have an id so include the type to open
|
|
ColumnsJSON = $"[ {{\"cm\":\"Widget\",\"dt\":{(int)AyaDataType.Text},\"ay\":{(int)AyaType.Widget}}}]";
|
|
//"[{\"cm\":\"WidgetName\",\"dt\":4},{\"cm\":\"WidgetSerial\",\"dt\":5},{\"cm\":\"WidgetDollarAmount\",\"dt\":8},{\"cm\":\"WidgetRoles\",\"dt\":10},{\"cm\":\"WidgetStartDate\",\"dt\":1},{\"cm\":\"Active\",\"dt\":6}]"
|
|
}
|
|
else
|
|
{
|
|
//TODO: need at least one column to be the openable object here or else nothing will open
|
|
ColumnsJSON = ObjectFields.GenerateListColumnJSONFromTemplate(ObjectFields.WIDGET_KEY, MOCK_WIDGET_DISPLAY_TEMPLATE_JSON);
|
|
}
|
|
|
|
//TODO: BUILD THE RETURN LIST OF DATA ITEMS
|
|
//If mini format all desired columns in order into the single mini return display (and set the only other return field which is ID)
|
|
|
|
//If wide then format the fields in oder chosen (grid sort and filter template has already done that part above)
|
|
|
|
//TODO: Genericize the above block of building return when it's working as this code needs to be central and optimized as much as possible
|
|
|
|
//New return code
|
|
// dynamic ret = new JObject();
|
|
// ret.items = new JArray.fromobj(items);
|
|
// ret.pageLinks = pageLinks;
|
|
// ret.columns=ColumnsJSON;
|
|
|
|
// return ret.ToString();
|
|
ApiPagedResponse pr = new ApiPagedResponse(items, pageLinks, ColumnsJSON);
|
|
return pr;
|
|
}
|
|
|
|
|
|
// //get many (paged)
|
|
// internal async Task<ApiPagedResponse<Widget>> GetManyAsync(IUrlHelper Url, string routeName, ListOptions pagingOptions)
|
|
// {
|
|
// pagingOptions.Offset = pagingOptions.Offset ?? ListOptions.DefaultOffset;
|
|
// pagingOptions.Limit = pagingOptions.Limit ?? ListOptions.DefaultLimit;
|
|
|
|
// //BUILD THE QUERY
|
|
// //base query
|
|
// var q = "SELECT *, xmin FROM AWIDGET ";
|
|
|
|
// //GET THE FILTER / SORT
|
|
// if (pagingOptions.DataFilterId > 0)
|
|
// {
|
|
// var TheFilter = await ct.DataFilter.FirstOrDefaultAsync(x => x.Id == pagingOptions.DataFilterId);
|
|
|
|
// //BUILD WHERE AND APPEND IT
|
|
// q = q + FilterSqlCriteriaBuilder.DataFilterToSQLCriteria(TheFilter, WidgetBiz.FilterOptions(), UserId);
|
|
|
|
// //BUILD ORDER BY AND APPEND IT
|
|
// q = q + FilterSqlOrderByBuilder.DataFilterToSQLOrderBy(TheFilter);
|
|
// }
|
|
// else
|
|
// {
|
|
// //GET DEFAULT ORDER BY
|
|
// q = q + FilterSqlOrderByBuilder.DefaultGetManyOrderBy();
|
|
// }
|
|
|
|
|
|
// #pragma warning disable EF1000
|
|
|
|
// var items = await ct.Widget
|
|
// .FromSqlRaw(q)
|
|
// .AsNoTracking()
|
|
// .Skip(pagingOptions.Offset.Value)
|
|
// .Take(pagingOptions.Limit.Value)
|
|
// .ToArrayAsync();
|
|
|
|
// var totalRecordCount = await ct.Widget
|
|
// .FromSqlRaw(q)
|
|
// .AsNoTracking()
|
|
// .CountAsync();
|
|
// #pragma warning restore EF1000
|
|
|
|
// var pageLinks = new PaginationLinkBuilder(Url, routeName, null, pagingOptions, totalRecordCount).PagingLinksObject();
|
|
|
|
// ApiPagedResponse<Widget> pr = new ApiPagedResponse<Widget>(items, pageLinks);
|
|
// return pr;
|
|
// }
|
|
|
|
|
|
// /// <summary>
|
|
// /// Get PickList
|
|
// /// </summary>
|
|
// /// <param name="Url"></param>
|
|
// /// <param name="routeName"></param>
|
|
// /// <param name="pagingOptions"></param>
|
|
// /// <returns></returns>
|
|
// internal ApiPagedResponse<NameIdItem> GetPickList(IUrlHelper Url, string routeName, ListOptions pagingOptions)
|
|
// {
|
|
// pagingOptions.Offset = pagingOptions.Offset ?? ListOptions.DefaultOffset;
|
|
// pagingOptions.Limit = pagingOptions.Limit ?? ListOptions.DefaultLimit;
|
|
// var ret = PickListFetcher.GetPickList(ct, UserId, pagingOptions, FilterOptions(), "awidget");
|
|
// var pageLinks = new PaginationLinkBuilder(Url, routeName, null, pagingOptions, ret.TotalRecordCount).PagingLinksObject();
|
|
// ApiPagedResponse<NameIdItem> pr = new ApiPagedResponse<NameIdItem>(ret.Items, pageLinks);
|
|
// return pr;
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//VALIDATION
|
|
//
|
|
|
|
//Can save or update?
|
|
private void Validate(Widget proposedObj, Widget currentObj)
|
|
{
|
|
|
|
//NOTE: In DB schema only name and serial are not nullable
|
|
|
|
//run validation and biz rules
|
|
bool isNew = currentObj == null;
|
|
|
|
// if (isNew)
|
|
// {
|
|
// //WARNING: this is not really the "current" object, it's been modified already by caller
|
|
|
|
// // //NEW widgets must be active
|
|
// // if (inObj.Active == null || ((bool)inObj.Active) == false)
|
|
// // {
|
|
// // AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Active", "New widget must be active");
|
|
// // }
|
|
// }
|
|
|
|
|
|
//Name required
|
|
if (string.IsNullOrWhiteSpace(proposedObj.Name))
|
|
AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name");
|
|
|
|
//Name must be less than 255 characters
|
|
if (proposedObj.Name.Length > 255)
|
|
AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "Name", "255 max");
|
|
|
|
//If name is otherwise OK, check that name is unique
|
|
if (!PropertyHasErrors("Name"))
|
|
{
|
|
//Use Any command is efficient way to check existance, it doesn't return the record, just a true or false
|
|
if (ct.Widget.Any(m => m.Name == proposedObj.Name && m.Id != proposedObj.Id))
|
|
{
|
|
AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name");
|
|
}
|
|
}
|
|
|
|
//Start date AND end date must both be null or both contain values
|
|
if (proposedObj.StartDate == null && proposedObj.EndDate != null)
|
|
AddError(ApiErrorCode.VALIDATION_REQUIRED, "StartDate");
|
|
|
|
if (proposedObj.StartDate != null && proposedObj.EndDate == null)
|
|
AddError(ApiErrorCode.VALIDATION_REQUIRED, "EndDate");
|
|
|
|
//Start date before end date
|
|
if (proposedObj.StartDate != null && proposedObj.EndDate != null)
|
|
if (proposedObj.StartDate > proposedObj.EndDate)
|
|
AddError(ApiErrorCode.VALIDATION_STARTDATE_AFTER_ENDDATE, "StartDate");
|
|
|
|
//Enum is valid value
|
|
//Note: because of the way the flags work as powers of 2 and with the 1 being the first flag value, basically any value up to the "All" value will be valid
|
|
//because you can make any number from 0 to all using any combination of the flags so the only thing that will fail is less than zero or greater than All
|
|
if (!proposedObj.Roles.IsValid())
|
|
{
|
|
AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Roles");
|
|
}
|
|
|
|
//Any form customizations to validate?
|
|
var FormCustomization = ct.FormCustom.SingleOrDefault(x => x.FormKey == ObjectFields.WIDGET_KEY);
|
|
if (FormCustomization != null)
|
|
{
|
|
//Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required
|
|
|
|
//validate users choices for required non custom fields
|
|
RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj);
|
|
|
|
//validate custom fields
|
|
CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields);
|
|
}
|
|
|
|
//for debug purposes
|
|
#if (DEBUG)
|
|
//TESTING
|
|
//make a fake server error for ui testing purposes
|
|
// if (proposedObj.Count == 666)
|
|
// {
|
|
// AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Count", "Test field server error");
|
|
// }
|
|
|
|
//removed because seeding huge kept triggering this
|
|
// if (proposedObj.DollarAmount == 666.66M)
|
|
// {
|
|
// AddError(ApiErrorCode.INVALID_OPERATION, null, "This is a test of a general server error");
|
|
// }
|
|
|
|
//TESTING
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//Can delete?
|
|
private void ValidateCanDelete(Widget inObj)
|
|
{
|
|
//whatever needs to be check to delete this object
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//JOB / OPERATIONS
|
|
//
|
|
public async Task HandleJobAsync(OpsJob job)
|
|
{
|
|
//Hand off the particular job to the corresponding processing code
|
|
//NOTE: If this code throws an exception the caller (JobsBiz::ProcessJobsAsync) will automatically set the job to failed and log the exeption so
|
|
//basically any error condition during job processing should throw up an exception if it can't be handled
|
|
switch (job.JobType)
|
|
{
|
|
case JobType.TestWidgetJob:
|
|
await ProcessTestJobAsync(job);
|
|
break;
|
|
default:
|
|
throw new System.ArgumentOutOfRangeException($"WidgetBiz.HandleJob-> Invalid job type{job.JobType.ToString()}");
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// /// Handle the test job
|
|
/// </summary>
|
|
/// <param name="job"></param>
|
|
private async Task ProcessTestJobAsync(OpsJob job)
|
|
{
|
|
var sleepTime = 30 * 1000;
|
|
//Simulate a long running job here
|
|
JobsBiz.UpdateJobStatus(job.GId, JobStatus.Running, ct);
|
|
JobsBiz.LogJob(job.GId, $"WidgetBiz::ProcessTestJob started, sleeping for {sleepTime} seconds...", ct);
|
|
//Uncomment this to test if the job prevents other routes from running
|
|
//result is NO it doesn't prevent other requests, so we are a-ok for now
|
|
await Task.Delay(sleepTime);
|
|
JobsBiz.LogJob(job.GId, "WidgetBiz::ProcessTestJob done sleeping setting job to finished", ct);
|
|
JobsBiz.UpdateJobStatus(job.GId, JobStatus.Completed, ct);
|
|
|
|
}
|
|
|
|
//Other job handlers here...
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
}//eoc
|
|
|
|
|
|
}//eons
|
|
|