diff --git a/src/Altinn.App.Api/Controllers/ApplicationMetadataController.cs b/src/Altinn.App.Api/Controllers/ApplicationMetadataController.cs index 7628fc3ac..e2892a693 100644 --- a/src/Altinn.App.Api/Controllers/ApplicationMetadataController.cs +++ b/src/Altinn.App.Api/Controllers/ApplicationMetadataController.cs @@ -1,5 +1,3 @@ -using System.Threading.Tasks; - using Altinn.App.Services.Interface; using Altinn.Platform.Storage.Interface.Models; diff --git a/src/Altinn.App.Api/Controllers/AuthenticationController.cs b/src/Altinn.App.Api/Controllers/AuthenticationController.cs index 45ce2f09b..f89ba2c12 100644 --- a/src/Altinn.App.Api/Controllers/AuthenticationController.cs +++ b/src/Altinn.App.Api/Controllers/AuthenticationController.cs @@ -5,7 +5,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace Altinn.App.Api.Controllers diff --git a/src/Altinn.App.Api/Controllers/DataController.cs b/src/Altinn.App.Api/Controllers/DataController.cs index a8f5bfa7c..e9438d087 100644 --- a/src/Altinn.App.Api/Controllers/DataController.cs +++ b/src/Altinn.App.Api/Controllers/DataController.cs @@ -14,6 +14,7 @@ using Altinn.App.Common.Helpers.Extensions; using Altinn.App.Common.Models; using Altinn.App.Common.Serialization; +using Altinn.App.Core.Interface; using Altinn.App.PlatformServices.Extensions; using Altinn.App.PlatformServices.Helpers; using Altinn.App.Services.Helpers; @@ -39,6 +40,7 @@ public class DataController : ControllerBase private readonly IData _dataClient; private readonly IInstance _instanceClient; private readonly IAltinnApp _altinnApp; + private readonly IAppModel _appModel; private readonly IAppResources _appResourcesService; private readonly IPrefill _prefillService; @@ -58,6 +60,7 @@ public DataController( IInstance instanceClient, IData dataClient, IAltinnApp altinnApp, + IAppModel appModel, IAppResources appResourcesService, IPrefill prefillService) { @@ -66,6 +69,7 @@ public DataController( _instanceClient = instanceClient; _dataClient = dataClient; _altinnApp = altinnApp; + _appModel = appModel; _appResourcesService = appResourcesService; _prefillService = prefillService; } @@ -383,11 +387,11 @@ private async Task CreateAppModelData( if (Request.ContentType == null) { - appModel = _altinnApp.CreateNewAppModel(classRef); + appModel = _appModel.Create(classRef); } else { - ModelDeserializer deserializer = new ModelDeserializer(_logger, _altinnApp.GetAppModelType(classRef)); + ModelDeserializer deserializer = new ModelDeserializer(_logger, _appModel.GetModelType(classRef)); appModel = await deserializer.DeserializeAsync(Request.Body, Request.ContentType); if (!string.IsNullOrEmpty(deserializer.Error)) @@ -399,23 +403,14 @@ private async Task CreateAppModelData( // runs prefill from repo configuration if config exists await _prefillService.PrefillDataModel(instance.InstanceOwner.PartyId, dataType, appModel); - // send events to trigger application business logic - try - { - await _altinnApp.RunDataCreation(instance, appModel, null); - } - catch (NotImplementedException) - { - // Trigger application business logic the old way. DEPRICATED - await _altinnApp.RunDataCreation(instance, appModel); - } + await _altinnApp.RunDataCreation(instance, appModel, null); await UpdatePresentationTextsOnInstance(instance, dataType, appModel); await UpdateDataValuesOnInstance(instance, dataType, appModel); int instanceOwnerPartyId = int.Parse(instance.InstanceOwner.PartyId); - DataElement dataElement = await _dataClient.InsertFormData(appModel, instanceGuid, _altinnApp.GetAppModelType(classRef), org, app, instanceOwnerPartyId, dataType); + DataElement dataElement = await _dataClient.InsertFormData(appModel, instanceGuid, _appModel.GetModelType(classRef), org, app, instanceOwnerPartyId, dataType); SelfLinkHelper.SetDataAppSelfLinks(instanceOwnerPartyId, instanceGuid, dataElement, Request); return Created(dataElement.SelfLinks.Apps, dataElement); @@ -502,7 +497,7 @@ private async Task GetFormData( // Get Form Data from data service. Assumes that the data element is form data. object appModel = await _dataClient.GetFormData( instanceGuid, - _altinnApp.GetAppModelType(appModelclassRef), + _appModel.GetModelType(appModelclassRef), org, app, instanceOwnerId, @@ -539,7 +534,7 @@ private async Task PutFormData(string org, string app, Instance in string classRef = _appResourcesService.GetClassRefForLogicDataType(dataType); Guid instanceGuid = Guid.Parse(instance.Id.Split("/")[1]); - ModelDeserializer deserializer = new ModelDeserializer(_logger, _altinnApp.GetAppModelType(classRef)); + ModelDeserializer deserializer = new ModelDeserializer(_logger, _appModel.GetModelType(classRef)); object serviceModel = await deserializer.DeserializeAsync(Request.Body, Request.ContentType); if (!string.IsNullOrEmpty(deserializer.Error)) @@ -562,7 +557,7 @@ private async Task PutFormData(string org, string app, Instance in DataElement updatedDataElement = await _dataClient.UpdateData( serviceModel, instanceGuid, - _altinnApp.GetAppModelType(classRef), + _appModel.GetModelType(classRef), org, app, instanceOwnerPartyId, diff --git a/src/Altinn.App.Api/Controllers/InstancesController.cs b/src/Altinn.App.Api/Controllers/InstancesController.cs index c379d20d3..f5a1ac983 100644 --- a/src/Altinn.App.Api/Controllers/InstancesController.cs +++ b/src/Altinn.App.Api/Controllers/InstancesController.cs @@ -18,7 +18,6 @@ using Altinn.App.PlatformServices.Extensions; using Altinn.App.PlatformServices.Helpers; using Altinn.App.PlatformServices.Interface; -using Altinn.App.PlatformServices.Models; using Altinn.App.Services.Configuration; using Altinn.App.Services.Helpers; using Altinn.App.Services.Interface; @@ -65,6 +64,7 @@ public class InstancesController : ControllerBase private readonly IAppResources _appResourcesService; private readonly IAltinnApp _altinnApp; + private readonly IAppModel _appModel; private readonly IPDP _pdp; private readonly IPrefill _prefillService; private readonly IProcessEngine _processEngine; @@ -82,6 +82,7 @@ public InstancesController( IData dataClient, IAppResources appResourcesService, IAltinnApp altinnApp, + IAppModel appModel, IPDP pdp, IEvents eventsService, IOptions appSettings, @@ -95,6 +96,7 @@ public InstancesController( _appResourcesService = appResourcesService; _registerClient = registerClient; _altinnApp = altinnApp; + _appModel = appModel; _pdp = pdp; _eventsService = eventsService; _appSettings = appSettings.Value; @@ -520,7 +522,7 @@ private async Task CopyDataFromSourceInstance(Application application, Instance Type type; try { - type = _altinnApp.GetAppModelType(dt.AppLogic.ClassRef); + type = _appModel.GetModelType(dt.AppLogic.ClassRef); } catch (Exception altinnAppException) { @@ -535,16 +537,8 @@ private async Task CopyDataFromSourceInstance(Application application, Instance } await _prefillService.PrefillDataModel(instanceOwnerPartyId.ToString(), dt.Id, data); - - try - { - await _altinnApp.RunDataCreation(targetInstance, data, null); - } - catch (NotImplementedException) - { - // Trigger application business logic the old way. DEPRECATED - await _altinnApp.RunDataCreation(targetInstance, data); - } + + await _altinnApp.RunDataCreation(targetInstance, data, null); await _dataClient.InsertFormData( data, @@ -854,7 +848,7 @@ private async Task StorePrefillParts(Instance instance, Application appInfo, Lis Type type; try { - type = _altinnApp.GetAppModelType(dataType.AppLogic.ClassRef); + type = _appModel.GetModelType(dataType.AppLogic.ClassRef); } catch (Exception altinnAppException) { @@ -870,15 +864,8 @@ private async Task StorePrefillParts(Instance instance, Application appInfo, Lis } await _prefillService.PrefillDataModel(instance.InstanceOwner.PartyId, part.Name, data); - try - { - await _altinnApp.RunDataCreation(instance, data, null); - } - catch (NotImplementedException) - { - // Trigger application business logic the old way. DEPRECATED - await _altinnApp.RunDataCreation(instance, data); - } + + await _altinnApp.RunDataCreation(instance, data, null); dataElement = await _dataClient.InsertFormData( data, diff --git a/src/Altinn.App.Api/Controllers/PagesController.cs b/src/Altinn.App.Api/Controllers/PagesController.cs index f5ead0899..7f13b391d 100644 --- a/src/Altinn.App.Api/Controllers/PagesController.cs +++ b/src/Altinn.App.Api/Controllers/PagesController.cs @@ -2,18 +2,15 @@ using System.Collections.Generic; using System.Threading.Tasks; -using Altinn.App.Common.Serialization; +using Altinn.App.Core.Interface; using Altinn.App.PlatformServices.Models; -using Altinn.App.Services.Implementation; using Altinn.App.Services.Interface; -using Altinn.Platform.Storage.Interface.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; namespace Altinn.App.Api.Controllers { @@ -25,9 +22,9 @@ namespace Altinn.App.Api.Controllers [Route("{org}/{app}/instances/{instanceOwnerPartyId:int}/{instanceGuid:guid}/pages")] public class PagesController : ControllerBase { - private readonly IAltinnApp _altinnApp; - private readonly IPageOrder _pageOrder; + private readonly IAppModel _appModel; private readonly IAppResources _resources; + private readonly IPageOrder _pageOrder; private readonly ILogger _logger; /// @@ -37,9 +34,13 @@ public class PagesController : ControllerBase /// The app resource service /// A logger provided by the logging framework. /// The page order service - public PagesController(IAltinnApp altinnApp, IAppResources resources, IPageOrder pageOrder, ILogger logger) + public PagesController( + IAppModel appModel, + IAppResources resources, + IPageOrder pageOrder, + ILogger logger) { - _altinnApp = altinnApp; + _appModel = appModel; _resources = resources; _pageOrder = pageOrder; _logger = logger; @@ -67,7 +68,7 @@ public async Task>> GetPageOrder( string classRef = _resources.GetClassRefForLogicDataType(dataTypeId); - object data = JsonConvert.DeserializeObject(formData.ToString(), _altinnApp.GetAppModelType(classRef)); + object data = JsonConvert.DeserializeObject(formData.ToString(), _appModel.GetModelType(classRef)); return await _pageOrder.GetPageOrder(new AppIdentifier(org, app), new InstanceIdentifier(instanceOwnerPartyId, instanceGuid), layoutSetId, currentPage, dataTypeId, data); } } diff --git a/src/Altinn.App.Api/Controllers/StatelessDataController.cs b/src/Altinn.App.Api/Controllers/StatelessDataController.cs index 5efb346f9..0bff38f9d 100644 --- a/src/Altinn.App.Api/Controllers/StatelessDataController.cs +++ b/src/Altinn.App.Api/Controllers/StatelessDataController.cs @@ -4,6 +4,7 @@ using Altinn.App.Api.Filters; using Altinn.App.Common.Serialization; +using Altinn.App.Core.Interface; using Altinn.App.PlatformServices.Extensions; using Altinn.App.Services.Interface; using Altinn.Authorization.ABAC.Xacml.JsonProfile; @@ -31,6 +32,7 @@ public class StatelessDataController : ControllerBase { private readonly ILogger _logger; private readonly IAltinnApp _altinnApp; + private readonly IAppModel _appModel; private readonly IAppResources _appResourcesService; private readonly IPrefill _prefillService; private readonly IRegister _registerClient; @@ -49,6 +51,7 @@ public class StatelessDataController : ControllerBase public StatelessDataController( ILogger logger, IAltinnApp altinnApp, + IAppModel appModel, IAppResources appResourcesService, IPrefill prefillService, IRegister registerClient, @@ -56,6 +59,7 @@ public StatelessDataController( { _logger = logger; _altinnApp = altinnApp; + _appModel = appModel; _appResourcesService = appResourcesService; _prefillService = prefillService; _registerClient = registerClient; @@ -111,7 +115,7 @@ public async Task Get( return Forbidden(enforcementResult); } - object appModel = _altinnApp.CreateNewAppModel(classRef); + object appModel = _appModel.Create(classRef); // runs prefill from repo configuration if config exists await _prefillService.PrefillDataModel(owner.PartyId, dataType, appModel); @@ -148,7 +152,7 @@ public async Task GetAnonymous([FromQuery] string dataType) return BadRequest($"Invalid dataType {dataType} provided. Please provide a valid dataType as query parameter."); } - object appModel = _altinnApp.CreateNewAppModel(classRef); + object appModel = _appModel.Create(classRef); var virutalInstance = new Instance(); await _altinnApp.RunProcessDataRead(virutalInstance, null, appModel); @@ -205,7 +209,7 @@ public async Task Post( return Forbidden(enforcementResult); } - ModelDeserializer deserializer = new ModelDeserializer(_logger, _altinnApp.GetAppModelType(classRef)); + ModelDeserializer deserializer = new ModelDeserializer(_logger, _appModel.GetModelType(classRef)); object appModel = await deserializer.DeserializeAsync(Request.Body, Request.ContentType); if (!string.IsNullOrEmpty(deserializer.Error)) @@ -248,7 +252,7 @@ public async Task PostAnonymous([FromQuery] string dataType) return BadRequest($"Invalid dataType {dataType} provided. Please provide a valid dataType as query parameter."); } - ModelDeserializer deserializer = new ModelDeserializer(_logger, _altinnApp.GetAppModelType(classRef)); + ModelDeserializer deserializer = new ModelDeserializer(_logger, _appModel.GetModelType(classRef)); object appModel = await deserializer.DeserializeAsync(Request.Body, Request.ContentType); if (!string.IsNullOrEmpty(deserializer.Error)) diff --git a/src/Altinn.App.Api/Controllers/StatelessPagesController.cs b/src/Altinn.App.Api/Controllers/StatelessPagesController.cs index fe5cdc9fb..87703b6f6 100644 --- a/src/Altinn.App.Api/Controllers/StatelessPagesController.cs +++ b/src/Altinn.App.Api/Controllers/StatelessPagesController.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; - +using Altinn.App.Core.Interface; using Altinn.App.PlatformServices.Models; using Altinn.App.Services.Interface; @@ -19,19 +19,22 @@ namespace Altinn.App.Api.Controllers [AllowAnonymous] public class StatelessPagesController : ControllerBase { - private readonly IAltinnApp _altinnApp; + private readonly IAppModel _appModel; private readonly IAppResources _resources; private readonly IPageOrder _pageOrder; /// /// Initializes a new instance of the class. /// - /// The current App Core used to interface with custom logic + /// The current appmodel implementation for getting the model Type /// The app resource service /// The page order service - public StatelessPagesController(IAltinnApp altinnApp, IAppResources resources, IPageOrder pageOrder) + public StatelessPagesController( + IAppModel appModel, + IAppResources resources, + IPageOrder pageOrder) { - _altinnApp = altinnApp; + _appModel = appModel; _resources = resources; _pageOrder = pageOrder; } @@ -56,7 +59,7 @@ public async Task>> GetPageOrder( string classRef = _resources.GetClassRefForLogicDataType(dataTypeId); - object data = JsonConvert.DeserializeObject(formData.ToString(), _altinnApp.GetAppModelType(classRef)); + object data = JsonConvert.DeserializeObject(formData.ToString(), _appModel.GetModelType(classRef)); return await _pageOrder.GetPageOrder(new AppIdentifier(org, app), InstanceIdentifier.NoInstance, layoutSetId, currentPage, dataTypeId, data); } } diff --git a/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs b/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs index 2859400b4..5538c2565 100644 --- a/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs +++ b/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs @@ -1,5 +1,3 @@ -using System; - using Altinn.App.Core.Implementation; using Altinn.App.Core.Infrastructure.Clients.Register; using Altinn.App.Core.Infrastructure.Clients.Storage; @@ -15,7 +13,6 @@ using Altinn.App.Services.Interface; using Altinn.Common.AccessTokenClient.Configuration; using Altinn.Common.AccessTokenClient.Services; -using Altinn.Common.EFormidlingClient; using Altinn.Common.PEP.Implementation; using Altinn.Common.PEP.Interfaces; @@ -25,6 +22,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; namespace Altinn.App.PlatformServices.Extensions @@ -68,6 +66,7 @@ public static void AddPlatformServices(this IServiceCollection services, IConfig services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); } /// @@ -83,11 +82,14 @@ public static void AddAppServices(this IServiceCollection services, IConfigurati services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddHttpClient(); services.AddSingleton(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); + services.TryAddTransient(); + services.TryAddTransient(); + services.TryAddTransient(); + services.TryAddTransient(); + services.TryAddTransient(); services.Configure(configuration.GetSection("PEPSettings")); services.Configure(configuration.GetSection("PlatformSettings")); services.Configure(configuration.GetSection("AccessTokenSettings")); @@ -130,7 +132,7 @@ private static void AddPdfServices(IServiceCollection services) // handler registered. // If someone wants to customize pdf formatting the PdfHandler class in the // app should be used and registered in the DI container. - services.AddTransient(); + services.TryAddTransient(); } private static void AddAppOptions(IServiceCollection services) diff --git a/src/Altinn.App.Core/Implementation/AppBase.cs b/src/Altinn.App.Core/Implementation/AppBase.cs index 16710db0e..e9382a518 100644 --- a/src/Altinn.App.Core/Implementation/AppBase.cs +++ b/src/Altinn.App.Core/Implementation/AppBase.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; - -using Altinn.App.Common.Models; +using Altinn.App.Core.Interface; using Altinn.App.PlatformServices.Interface; using Altinn.App.Services.Configuration; using Altinn.App.Services.Constants; @@ -15,9 +9,7 @@ using Altinn.Common.EFormidlingClient; using Altinn.Common.EFormidlingClient.Models.SBD; using Altinn.Platform.Storage.Interface.Models; - using AltinnCore.Authentication.Utils; - using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.Logging; @@ -28,10 +20,9 @@ namespace Altinn.App.Services.Implementation /// /// Default implementation of the core Altinn App interface. /// - public abstract class AppBase : IAltinnApp + public class AppBase : IAltinnApp { private readonly Application _appMetadata; - private readonly IAppResources _resourceService; private readonly ILogger _logger; private readonly IEFormidlingClient _eFormidlingClient; private readonly IHttpContextAccessor _httpContextAccessor; @@ -42,6 +33,11 @@ public abstract class AppBase : IAltinnApp private readonly IInstance _instanceClient; private readonly IAccessTokenGenerator _tokenGenerator; private readonly PlatformSettings _platformSettings; + private readonly IInstanceValidator _instanceValidator; + private readonly IInstantiation _instantiation; + private readonly IDataProcessor _dataProcessor; + private readonly ITaskProcessor _taskProcessor; + private readonly IAppModel _appModel; private readonly string _org; private readonly string _app; @@ -60,7 +56,7 @@ public abstract class AppBase : IAltinnApp /// The appsettings /// The platform settings /// The access token generator - protected AppBase( + public AppBase( IAppResources resourceService, ILogger logger, IData dataClient, @@ -68,13 +64,17 @@ protected AppBase( IPrefill prefillService, IInstance instanceClient, IHttpContextAccessor httpContextAccessor, + IInstantiation instantiation, + IInstanceValidator instanceValidator, + IDataProcessor dataProcessor, + ITaskProcessor taskProcessor, + IAppModel appModel, IEFormidlingClient eFormidlingClient = null, IOptions appSettings = null, IOptions platformSettings = null, IAccessTokenGenerator tokenGenerator = null) { _appMetadata = resourceService.GetApplication(); - _resourceService = resourceService; _logger = logger; _dataClient = dataClient; _pdfService = pdfService; @@ -85,52 +85,57 @@ protected AppBase( _eFormidlingClient = eFormidlingClient; _tokenGenerator = tokenGenerator; _platformSettings = platformSettings?.Value; + _instanceValidator = instanceValidator; + _instantiation = instantiation; + _dataProcessor = dataProcessor; + _taskProcessor = taskProcessor; + _appModel = appModel; _org = _appMetadata.Org; _app = _appMetadata.Id.Split("/")[1]; } /// - public abstract Type GetAppModelType(string classRef); - - /// - public abstract object CreateNewAppModel(string classRef); - - /// - public abstract Task RunDataValidation(object data, ModelStateDictionary validationResults); - - /// - public abstract Task RunTaskValidation(Instance instance, string taskId, ModelStateDictionary validationResults); + public async Task RunDataValidation(object data, ModelStateDictionary validationResults) + { + await _instanceValidator.ValidateData(data, validationResults); + } /// - public virtual Task RunProcessDataRead(Instance instance, Guid? dataId, object data) + public async Task RunTaskValidation(Instance instance, string taskId, ModelStateDictionary validationResults) { - return Task.FromResult(false); + await _instanceValidator.ValidateTask(instance, taskId, validationResults); } /// - public virtual Task RunProcessDataWrite(Instance instance, Guid? dataId, object data) + public async Task RunProcessDataRead(Instance instance, Guid? dataId, object data) { - return Task.FromResult(false); + return await _dataProcessor.ProcessDataRead(instance, dataId, data); } /// - public abstract Task RunInstantiationValidation(Instance instance); + public async Task RunProcessDataWrite(Instance instance, Guid? dataId, object data) + { + return await _dataProcessor.ProcessDataWrite(instance, dataId, data); + } /// - public virtual Task RunDataCreation(Instance instance, object data) + public async Task RunInstantiationValidation(Instance instance) { - return Task.CompletedTask; + return await _instantiation.Validation(instance); } /// - public virtual Task RunDataCreation(Instance instance, object data, Dictionary prefill) + public async Task RunDataCreation(Instance instance, object data, Dictionary prefill) { - throw new NotImplementedException("RunDataCreation with external prefill not implemented in app"); + await _instantiation.DataCreation(instance, data, prefill); } /// - public abstract Task RunProcessTaskEnd(string taskId, Instance instance); + public async Task RunProcessTaskEnd(string taskId, Instance instance) + { + await _taskProcessor.ProcessTaskEnd(taskId, instance); + } /// public Task OnInstantiateGetStartEvent() @@ -174,7 +179,8 @@ public async Task OnStartProcessTask(string taskId, Instance instance, Dictionar } } - foreach (DataType dataType in _appMetadata.DataTypes.Where(dt => dt.TaskId == taskId && dt.AppLogic?.AutoCreate == true)) + foreach (DataType dataType in _appMetadata.DataTypes.Where(dt => + dt.TaskId == taskId && dt.AppLogic?.AutoCreate == true)) { _logger.LogInformation($"Auto create data element: {dataType.Id}"); @@ -182,23 +188,16 @@ public async Task OnStartProcessTask(string taskId, Instance instance, Dictionar if (dataElement == null) { - dynamic data = CreateNewAppModel(dataType.AppLogic.ClassRef); + dynamic data = _appModel.Create(dataType.AppLogic.ClassRef); // runs prefill from repo configuration if config exists await _prefillService.PrefillDataModel(instance.InstanceOwner.PartyId, dataType.Id, data, prefill); - try - { - await RunDataCreation(instance, data, prefill); - } - catch (NotImplementedException) - { - // Trigger application business logic the old way. DEPRICATED - await RunDataCreation(instance, data); - } + await RunDataCreation(instance, data, prefill); - Type type = GetAppModelType(dataType.AppLogic.ClassRef); + Type type = _appModel.GetModelType(dataType.AppLogic.ClassRef); - DataElement createdDataElement = await _dataClient.InsertFormData(instance, dataType.Id, data, type); + DataElement createdDataElement = + await _dataClient.InsertFormData(instance, dataType.Id, data, type); instance.Data.Add(createdDataElement); await UpdatePresentationTextsOnInstance(instance, dataType.Id, data); @@ -210,7 +209,8 @@ public async Task OnStartProcessTask(string taskId, Instance instance, Dictionar } /// - public async Task CanEndProcessTask(string taskId, Instance instance, List validationIssues) + public async Task CanEndProcessTask(string taskId, Instance instance, + List validationIssues) { // check if the task is validated if (instance.Process?.CurrentTask?.Validated != null) @@ -255,8 +255,9 @@ public async Task OnEndProcessTask(string taskId, Instance instance) if (generatePdf) { - Type dataElementType = GetAppModelType(dataType.AppLogic.ClassRef); - Task createPdf = _pdfService.GenerateAndStoreReceiptPDF(instance, taskId, dataElement, dataElementType); + Type dataElementType = _appModel.GetModelType(dataType.AppLogic.ClassRef); + Task createPdf = + _pdfService.GenerateAndStoreReceiptPDF(instance, taskId, dataElement, dataElementType); await Task.WhenAll(updateData, createPdf); } else @@ -266,11 +267,13 @@ public async Task OnEndProcessTask(string taskId, Instance instance) } } - if (_appSettings != null && _platformSettings != null && _appSettings.EnableEFormidling && _appMetadata.EFormidling.SendAfterTaskId == taskId) + if (_appSettings != null && _platformSettings != null && _appSettings.EnableEFormidling && + _appMetadata.EFormidling.SendAfterTaskId == taskId) { if (_eFormidlingClient == null || _tokenGenerator == null) { - throw new EntryPointNotFoundException("eFormidling support has not been correctly configured in App.cs. " + + throw new EntryPointNotFoundException( + "eFormidling support has not been correctly configured in App.cs. " + "Ensure that IEformidlingClient and IAccessTokenGenerator are included in the base constructor."); } @@ -291,31 +294,16 @@ public async Task OnAbandonProcessTask(string taskId, Instance instance) { await RunProcessTaskEnd(taskId, instance); - _logger.LogInformation($"OnAbandonProcessTask for {instance.Id}. Locking data elements connected to {taskId}"); + _logger.LogInformation( + $"OnAbandonProcessTask for {instance.Id}. Locking data elements connected to {taskId}"); await Task.CompletedTask; } - /// - public virtual async Task> GetPageOrder(string org, string app, int instanceOwnerId, Guid instanceGuid, string layoutSetId, string currentPage, string dataTypeId, object formData) - { - LayoutSettings layoutSettings = null; - - if (string.IsNullOrEmpty(layoutSetId)) - { - layoutSettings = _resourceService.GetLayoutSettings(); - } - else - { - layoutSettings = _resourceService.GetLayoutSettingsForSet(layoutSetId); - } - - return await Task.FromResult(layoutSettings.Pages.Order); - } - /// public virtual Task<(string MetadataFilename, Stream Metadata)> GenerateEFormidlingMetadata(Instance instance) { - throw new NotImplementedException("No method available for generating arkivmelding for eFormidling shipment."); + throw new NotImplementedException( + "No method available for generating arkivmelding for eFormidling shipment."); } /// @@ -345,9 +333,9 @@ private async Task UpdatePresentationTextsOnInstance(Instance instance, string d if (updatedValues.Count > 0) { var updatedInstance = await _instanceClient.UpdatePresentationTexts( - int.Parse(instance.Id.Split("/")[0]), - Guid.Parse(instance.Id.Split("/")[1]), - new PresentationTexts { Texts = updatedValues }); + int.Parse(instance.Id.Split("/")[0]), + Guid.Parse(instance.Id.Split("/")[1]), + new PresentationTexts { Texts = updatedValues }); instance.PresentationTexts = updatedInstance.PresentationTexts; } @@ -384,21 +372,28 @@ private async Task SendInstanceData(Instance instance, Dictionary d.Id == dataElement.DataType && d.AppLogic?.ClassRef != null); + bool appLogic = + _appMetadata.DataTypes.Any(d => d.Id == dataElement.DataType && d.AppLogic?.ClassRef != null); string fileName = appLogic ? $"{dataElement.DataType}.xml" : dataElement.Filename; - using Stream stream = await _dataClient.GetBinaryData(_org, _app, instanceOwnerPartyId, instanceGuid, new Guid(dataElement.Id)); + using Stream stream = await _dataClient.GetBinaryData(_org, _app, instanceOwnerPartyId, instanceGuid, + new Guid(dataElement.Id)); - bool successful = await _eFormidlingClient.UploadAttachment(stream, instanceGuid.ToString(), fileName, requestHeaders); + bool successful = + await _eFormidlingClient.UploadAttachment(stream, instanceGuid.ToString(), fileName, + requestHeaders); if (!successful) { - _logger.LogError("// AppBase // SendInstanceData // DataElement {DataElementId} was not sent with shipment for instance {InstanceId} failed.", dataElement.Id, instance.Id); + _logger.LogError( + "// AppBase // SendInstanceData // DataElement {DataElementId} was not sent with shipment for instance {InstanceId} failed.", + dataElement.Id, instance.Id); } } } - private async Task ConstructStandardBusinessDocument(string instanceGuid, Instance instance) + private async Task ConstructStandardBusinessDocument(string instanceGuid, + Instance instance) { DateTime completedTime = DateTime.Now; @@ -415,19 +410,19 @@ private async Task ConstructStandardBusinessDocument(s List receivers = await GetEFormidlingReceivers(instance); Scope scope = - new Scope - { - Identifier = _appMetadata.EFormidling.Process, - InstanceIdentifier = Guid.NewGuid().ToString(), - Type = "ConversationId", - ScopeInformation = new List + new Scope + { + Identifier = _appMetadata.EFormidling.Process, + InstanceIdentifier = Guid.NewGuid().ToString(), + Type = "ConversationId", + ScopeInformation = new List { new ScopeInformation { ExpectedResponseDateTime = completedTime.AddHours(2) } }, - }; + }; BusinessScope businessScope = new BusinessScope { @@ -469,7 +464,8 @@ private async Task ConstructStandardBusinessDocument(s private async Task SendEFormidlingShipment(Instance instance) { string accessToken = _tokenGenerator.GenerateAccessToken(_org, _app); - string authzToken = JwtTokenUtil.GetTokenFromContext(_httpContextAccessor.HttpContext, _appSettings.RuntimeCookieName); + string authzToken = + JwtTokenUtil.GetTokenFromContext(_httpContextAccessor.HttpContext, _appSettings.RuntimeCookieName); var requestHeaders = new Dictionary { @@ -498,14 +494,16 @@ private async Task SendEFormidlingShipment(Instance instance) } catch { - _logger.LogError("// AppBase // SendEFormidlingShipment // Shipment of instance {InstanceId} failed.", instance.Id); + _logger.LogError("// AppBase // SendEFormidlingShipment // Shipment of instance {InstanceId} failed.", + instance.Id); throw; } } private async Task AutoDeleteDataElements(Instance instance) { - List typesToDelete = _appMetadata.DataTypes.Where(dt => dt?.AppLogic?.AutoDeleteOnProcessEnd == true).Select(dt => dt.Id).ToList(); + List typesToDelete = _appMetadata.DataTypes + .Where(dt => dt?.AppLogic?.AutoDeleteOnProcessEnd == true).Select(dt => dt.Id).ToList(); if (typesToDelete.Count == 0) { return; @@ -519,15 +517,15 @@ private async Task AutoDeleteDataElements(Instance instance) { deleteTasks.Add( _dataClient.DeleteData( - _org, - _app, - int.Parse(instance.InstanceOwner.PartyId), - Guid.Parse(item.InstanceGuid), - Guid.Parse(item.Id), - true)); + _org, + _app, + int.Parse(instance.InstanceOwner.PartyId), + Guid.Parse(item.InstanceGuid), + Guid.Parse(item.Id), + true)); } await Task.WhenAll(deleteTasks); } } -} +} \ No newline at end of file diff --git a/src/Altinn.App.Core/Implementation/DefaultPageOrder.cs b/src/Altinn.App.Core/Implementation/DefaultPageOrder.cs index 3dcfae9c0..d7f77ac98 100644 --- a/src/Altinn.App.Core/Implementation/DefaultPageOrder.cs +++ b/src/Altinn.App.Core/Implementation/DefaultPageOrder.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Altinn.App.Common.Models; +using Altinn.App.Common.Models; using Altinn.App.PlatformServices.Models; using Altinn.App.Services.Interface; @@ -29,31 +26,18 @@ public DefaultPageOrder(IAltinnApp altinnApp, IAppResources resources) /// public async Task> GetPageOrder(AppIdentifier appIdentifier, InstanceIdentifier instanceIdentifier, string layoutSetId, string currentPage, string dataTypeId, object formData) { - if (instanceIdentifier.IsNoInstance) - { - LayoutSettings layoutSettings = null; - - if (string.IsNullOrEmpty(layoutSetId)) - { - layoutSettings = _resources.GetLayoutSettings(); - } - else - { - layoutSettings = _resources.GetLayoutSettingsForSet(layoutSetId); - } + LayoutSettings layoutSettings = null; - return await Task.FromResult(layoutSettings.Pages.Order); + if (string.IsNullOrEmpty(layoutSetId)) + { + layoutSettings = _resources.GetLayoutSettings(); + } + else + { + layoutSettings = _resources.GetLayoutSettingsForSet(layoutSetId); } - return await _altinnApp.GetPageOrder( - appIdentifier.Org, - appIdentifier.App, - instanceIdentifier.InstanceOwnerPartyId, - instanceIdentifier.InstanceGuid, - layoutSetId, - currentPage, - dataTypeId, - formData); + return await Task.FromResult(layoutSettings.Pages.Order); } } } diff --git a/src/Altinn.App.Core/Implementation/NullDataProcessor.cs b/src/Altinn.App.Core/Implementation/NullDataProcessor.cs new file mode 100644 index 000000000..4abb84db0 --- /dev/null +++ b/src/Altinn.App.Core/Implementation/NullDataProcessor.cs @@ -0,0 +1,23 @@ +using Altinn.App.Core.Interface; +using Altinn.Platform.Storage.Interface.Models; + +namespace Altinn.App.Core.Implementation; + +/// +/// Default implementation of the IDataProcessor interface. +/// This implementation does not do any thing to the data +/// +public class NullDataProcessor: IDataProcessor +{ + /// + public async Task ProcessDataRead(Instance instance, Guid? dataId, object data) + { + return await Task.FromResult(false); + } + + /// + public async Task ProcessDataWrite(Instance instance, Guid? dataId, object data) + { + return await Task.FromResult(false); + } +} \ No newline at end of file diff --git a/src/Altinn.App.Core/Implementation/NullInstanceValidator.cs b/src/Altinn.App.Core/Implementation/NullInstanceValidator.cs new file mode 100644 index 000000000..a5a0a8111 --- /dev/null +++ b/src/Altinn.App.Core/Implementation/NullInstanceValidator.cs @@ -0,0 +1,24 @@ +using Altinn.App.Core.Interface; +using Altinn.Platform.Storage.Interface.Models; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Altinn.App.Core.Implementation; + +/// +/// Default implementation of the IInstanceValidator interface. +/// This implementation does not do any validation and always returns true. +/// +public class NullInstanceValidator: IInstanceValidator +{ + /// + public async Task ValidateData(object data, ModelStateDictionary validationResults) + { + await Task.CompletedTask; + } + + /// + public async Task ValidateTask(Instance instance, string taskId, ModelStateDictionary validationResults) + { + await Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/Altinn.App.Core/Implementation/NullInstantiation.cs b/src/Altinn.App.Core/Implementation/NullInstantiation.cs new file mode 100644 index 000000000..ac1f420ab --- /dev/null +++ b/src/Altinn.App.Core/Implementation/NullInstantiation.cs @@ -0,0 +1,24 @@ +using Altinn.App.Core.Interface; +using Altinn.App.Services.Models.Validation; +using Altinn.Platform.Storage.Interface.Models; + +namespace Altinn.App.Core.Implementation; + +/// +/// Default implementation of the IInstantiation interface. +/// This implementation does not do any thing to the data +/// +public class NullInstantiation: IInstantiation +{ + /// + public async Task Validation(Instance instance) + { + return await Task.FromResult((InstantiationValidationResult)null); + } + + /// + public async Task DataCreation(Instance instance, object data, Dictionary prefill) + { + await Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/Altinn.App.Core/Implementation/NullPdfHandler.cs b/src/Altinn.App.Core/Implementation/NullPdfFormatter.cs similarity index 78% rename from src/Altinn.App.Core/Implementation/NullPdfHandler.cs rename to src/Altinn.App.Core/Implementation/NullPdfFormatter.cs index 0b12f0f59..99a39c8b0 100644 --- a/src/Altinn.App.Core/Implementation/NullPdfHandler.cs +++ b/src/Altinn.App.Core/Implementation/NullPdfFormatter.cs @@ -5,9 +5,9 @@ namespace Altinn.App.PlatformServices.Implementation { /// - /// Null object for representing a custom PDF handler. + /// Null object for representing a custom PDF formatter. /// - public class NullPdfHandler : ICustomPdfHandler + public class NullPdfFormatter : IPdfFormatter { /// public Task FormatPdf(LayoutSettings layoutSettings, object data) diff --git a/src/Altinn.App.Core/Implementation/NullTaskProcessor.cs b/src/Altinn.App.Core/Implementation/NullTaskProcessor.cs new file mode 100644 index 000000000..88623f4f2 --- /dev/null +++ b/src/Altinn.App.Core/Implementation/NullTaskProcessor.cs @@ -0,0 +1,17 @@ +using Altinn.App.Core.Interface; +using Altinn.Platform.Storage.Interface.Models; + +namespace Altinn.App.Core.Implementation; + +/// +/// Default implementation of the ITaskProcessor interface. +/// This implementation does not do any thing on TaskEnd +/// +public class NullTaskProcessor: ITaskProcessor +{ + /// + public async Task ProcessTaskEnd(string taskId, Instance instance) + { + await Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/Altinn.App.Core/Implementation/PdfService.cs b/src/Altinn.App.Core/Implementation/PdfService.cs index 8457cf949..f4c356820 100644 --- a/src/Altinn.App.Core/Implementation/PdfService.cs +++ b/src/Altinn.App.Core/Implementation/PdfService.cs @@ -34,7 +34,7 @@ public class PdfService : IPdfService private readonly IHttpContextAccessor _httpContextAccessor; private readonly IProfile _profileClient; private readonly IRegister _registerClient; - private readonly ICustomPdfHandler _customPdfHandler; + private readonly IPdfFormatter _pdfFormatter; private readonly string pdfElementType = "ref-data-as-pdf"; /// @@ -47,8 +47,8 @@ public class PdfService : IPdfService /// The httpContextAccessor /// The profile client /// The register client - /// Class for customizing pdf formatting and layout. - public PdfService(IPDF pdfClient, IAppResources appResources, IAppOptionsService appOptionsService, IData dataClient, IHttpContextAccessor httpContextAccessor, IProfile profileClient, IRegister registerClient, ICustomPdfHandler customPdfHandler) + /// Class for customizing pdf formatting and layout. + public PdfService(IPDF pdfClient, IAppResources appResources, IAppOptionsService appOptionsService, IData dataClient, IHttpContextAccessor httpContextAccessor, IProfile profileClient, IRegister registerClient, IPdfFormatter pdfFormatter) { _pdfClient = pdfClient; _resourceService = appResources; @@ -57,7 +57,7 @@ public PdfService(IPDF pdfClient, IAppResources appResources, IAppOptionsService _httpContextAccessor = httpContextAccessor; _profileClient = profileClient; _registerClient = registerClient; - _customPdfHandler = customPdfHandler; + _pdfFormatter = pdfFormatter; } /// @@ -95,7 +95,7 @@ public async Task GenerateAndStoreReceiptPDF(Instance instance, string taskId, D object data = await _dataClient.GetFormData(instanceGuid, dataElementModelType, org, app, instanceOwnerId, new Guid(dataElement.Id)); - layoutSettings = await _customPdfHandler.FormatPdf(layoutSettings, data); + layoutSettings = await _pdfFormatter.FormatPdf(layoutSettings, data); XmlSerializer serializer = new XmlSerializer(dataElementModelType); using MemoryStream stream = new MemoryStream(); diff --git a/src/Altinn.App.Core/Implementation/ValidationAppSI.cs b/src/Altinn.App.Core/Implementation/ValidationAppSI.cs index 6da7016f3..5f28585ef 100644 --- a/src/Altinn.App.Core/Implementation/ValidationAppSI.cs +++ b/src/Altinn.App.Core/Implementation/ValidationAppSI.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - +using Altinn.App.Core.Interface; using Altinn.App.Services.Configuration; using Altinn.App.Services.Interface; using Altinn.App.Services.Models.Validation; @@ -28,6 +24,7 @@ public class ValidationAppSI : IValidation private readonly IData _dataService; private readonly IInstance _instanceService; private readonly IAltinnApp _altinnApp; + private readonly IAppModel _appModel; private readonly IAppResources _appResourcesService; private readonly IObjectModelValidator _objectModelValidator; private readonly IHttpContextAccessor _httpContextAccessor; @@ -41,6 +38,7 @@ public ValidationAppSI( IData dataService, IInstance instanceService, IAltinnApp altinnApp, + IAppModel appModel, IAppResources appResourcesService, IObjectModelValidator objectModelValidator, IHttpContextAccessor httpContextAccessor, @@ -50,6 +48,7 @@ public ValidationAppSI( _dataService = dataService; _instanceService = instanceService; _altinnApp = altinnApp; + _appModel = appModel; _appResourcesService = appResourcesService; _objectModelValidator = objectModelValidator; _httpContextAccessor = httpContextAccessor; @@ -182,7 +181,7 @@ public async Task> ValidateDataElement(Instance instance, if (dataType.AppLogic?.ClassRef != null) { - Type modelType = _altinnApp.GetAppModelType(dataType.AppLogic.ClassRef); + Type modelType = _appModel.GetModelType(dataType.AppLogic.ClassRef); Guid instanceGuid = Guid.Parse(instance.Id.Split("/")[1]); string app = instance.AppId.Split("/")[1]; int instanceOwnerPartyId = int.Parse(instance.InstanceOwner.PartyId); diff --git a/src/Altinn.App.Core/Interface/IAltinnApp.cs b/src/Altinn.App.Core/Interface/IAltinnApp.cs index f176bd110..2823915b4 100644 --- a/src/Altinn.App.Core/Interface/IAltinnApp.cs +++ b/src/Altinn.App.Core/Interface/IAltinnApp.cs @@ -17,17 +17,6 @@ namespace Altinn.App.Services.Interface /// public interface IAltinnApp { - /// - /// Creates a new Instance of the service model - /// - /// An instance of the service model - object CreateNewAppModel(string classRef); - - /// - /// Get the service Type - /// - /// The Type of the service model for the current service - Type GetAppModelType(string classRef); /// /// AppLogic must set the start event of the process model. @@ -111,34 +100,11 @@ public interface IAltinnApp /// Task with validation results Task RunInstantiationValidation(Instance instance); - /// - /// Is called to run data creation (custom prefill) defined by app developer. - /// - Task RunDataCreation(Instance instance, object data); - /// /// Is called to run data creation (custom prefill) defined by app developer. Includes external prefill /// Task RunDataCreation(Instance instance, object data, Dictionary prefill); - /// - /// Gets the current page order of the app - /// - /// The app owner. - /// The app. - /// The instance owner partyId - /// The instanceGuid - /// The layout set id - /// The current page of the instance. - /// The data type id of the current layout. - /// The form data. - /// The pages in sorted order. - [Obsolete("This method is deprecated. Use transient dependency of IStatefulPageOrder instead.", UrlFormat="https://docs.altinn.studio/app/development/ux/pages/tracks/")] - virtual async Task> GetPageOrder(string org, string app, int instanceOwnerId, Guid instanceGuid, string layoutSetId, string currentPage, string dataTypeId, object formData) - { - return await Task.FromResult(new List()); - } - /// /// Event where app developers can add logic. /// diff --git a/src/Altinn.App.Core/Interface/IAppModel.cs b/src/Altinn.App.Core/Interface/IAppModel.cs new file mode 100644 index 000000000..be7f649ef --- /dev/null +++ b/src/Altinn.App.Core/Interface/IAppModel.cs @@ -0,0 +1,18 @@ +namespace Altinn.App.Core.Interface; + +/// +/// This interface is used to define the methods that are used to instantiate the applications data model. +/// +public interface IAppModel +{ + /// + /// Creates a new Instance of the service model + /// + /// An instance of the service model + public object Create(string classRef); + /// + /// Get the service Type + /// + /// The Type of the service model for the current service + public Type GetModelType(string classRef); +} \ No newline at end of file diff --git a/src/Altinn.App.Core/Interface/IDataProcessor.cs b/src/Altinn.App.Core/Interface/IDataProcessor.cs new file mode 100644 index 000000000..a9b1b900b --- /dev/null +++ b/src/Altinn.App.Core/Interface/IDataProcessor.cs @@ -0,0 +1,25 @@ +using Altinn.Platform.Storage.Interface.Models; + +namespace Altinn.App.Core.Interface; + +/// +/// This interface defines all the methods that are required for overriding DataProcessing calls. +/// +public interface IDataProcessor +{ + /// + /// Is called to run custom calculation events defined by app developer when data is read from app + /// + /// Instance that data belongs to + /// Data id for the data + /// The data to perform calculations on + public Task ProcessDataRead(Instance instance, Guid? dataId, object data); + + /// + /// Is called to run custom calculation events defined by app developer when data is written to app + /// + /// Instance that data belongs to + /// Data id for the data + /// The data to perform calculations on + public Task ProcessDataWrite(Instance instance, Guid? dataId, object data); +} \ No newline at end of file diff --git a/src/Altinn.App.Core/Interface/IInstanceValidator.cs b/src/Altinn.App.Core/Interface/IInstanceValidator.cs new file mode 100644 index 000000000..aa433bfce --- /dev/null +++ b/src/Altinn.App.Core/Interface/IInstanceValidator.cs @@ -0,0 +1,27 @@ +using Altinn.Platform.Storage.Interface.Models; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Altinn.App.Core.Interface; + +/// +/// IInstanceValidator defines the methods that are used to validate data and tasks +/// +public interface IInstanceValidator +{ + /// + /// Is called to run custom data validation events. + /// + /// The data to validate + /// Object containing any validation errors/warnings + /// Task to indicate when validation is completed + public Task ValidateData(object data, ModelStateDictionary validationResults); + + /// + /// Is called to run custom task validation events. + /// + /// Instance to be validated. + /// Task id for the current process task. + /// Object containing any validation errors/warnings + /// Task to indicate when validation is completed + public Task ValidateTask(Instance instance, string taskId, ModelStateDictionary validationResults); +} \ No newline at end of file diff --git a/src/Altinn.App.Core/Interface/IInstantiation.cs b/src/Altinn.App.Core/Interface/IInstantiation.cs new file mode 100644 index 000000000..59baf4861 --- /dev/null +++ b/src/Altinn.App.Core/Interface/IInstantiation.cs @@ -0,0 +1,35 @@ +using Altinn.App.Services.Models.Validation; +using Altinn.Platform.Storage.Interface.Models; + +namespace Altinn.App.Core.Interface; + +/// +/// IInstantiation defines the methods that must be implemented by a class that handles custom logic during instantiation of a new instance. +/// +public interface IInstantiation +{ + /// + /// Run validations related to instantiation + /// + /// + /// if ([some condition]) + /// { + /// return new ValidationResult("[error message]"); + /// } + /// return null; + /// + /// The instance being validated + /// The validation result object (null if no errors) + public Task Validation(Instance instance); + + /// + /// Run events related to instantiation + /// + /// + /// For example custom prefill. + /// + /// Instance information + /// The data object created + /// External prefill available under instansiation if supplied + public Task DataCreation(Instance instance, object data, Dictionary prefill); +} \ No newline at end of file diff --git a/src/Altinn.App.Core/Interface/ICustomPdfHandler.cs b/src/Altinn.App.Core/Interface/IPdfFormatter.cs similarity index 91% rename from src/Altinn.App.Core/Interface/ICustomPdfHandler.cs rename to src/Altinn.App.Core/Interface/IPdfFormatter.cs index f402ccc3b..48cc92bd1 100644 --- a/src/Altinn.App.Core/Interface/ICustomPdfHandler.cs +++ b/src/Altinn.App.Core/Interface/IPdfFormatter.cs @@ -6,7 +6,7 @@ namespace Altinn.App.PlatformServices.Interface /// /// Interface to customize PDF formatting. /// - public interface ICustomPdfHandler + public interface IPdfFormatter { /// /// Method to format the PDF dynamically diff --git a/src/Altinn.App.Core/Interface/ITaskProcessor.cs b/src/Altinn.App.Core/Interface/ITaskProcessor.cs new file mode 100644 index 000000000..fed4d74d4 --- /dev/null +++ b/src/Altinn.App.Core/Interface/ITaskProcessor.cs @@ -0,0 +1,16 @@ +using Altinn.Platform.Storage.Interface.Models; + +namespace Altinn.App.Core.Interface; + +/// +/// ITaskProcessor defines the methods that must be implemented by a task processing handler. +/// +public interface ITaskProcessor +{ + /// + /// Method for defining custom processing on TaskEnded event. + /// + /// The taskId + /// The instance + public Task ProcessTaskEnd(string taskId, Instance instance); +} \ No newline at end of file diff --git a/test/Altinn.App.Api.Tests/Controllers/StatelessDataControllerTests.cs b/test/Altinn.App.Api.Tests/Controllers/StatelessDataControllerTests.cs index f79698c00..cd371b5b2 100644 --- a/test/Altinn.App.Api.Tests/Controllers/StatelessDataControllerTests.cs +++ b/test/Altinn.App.Api.Tests/Controllers/StatelessDataControllerTests.cs @@ -1,19 +1,15 @@ -using System; using System.Collections.Generic; using System.Security.Claims; using Altinn.App.Api.Controllers; using Altinn.App.Api.Tests.Controllers.TestResources; -using Altinn.App.PlatformServices.Extensions; +using Altinn.App.Core.Interface; using Altinn.App.Services.Interface; using Altinn.Authorization.ABAC.Xacml; using Altinn.Authorization.ABAC.Xacml.JsonProfile; using Altinn.Common.PEP.Interfaces; -using Altinn.Platform.Register.Models; using Altinn.Platform.Storage.Interface.Models; using FluentAssertions; -using Microsoft.ApplicationInsights.DataContracts; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -30,12 +26,13 @@ public async void Get_Returns_BadRequest_when_dataType_is_null() { // Arrange var altinnAppMock = new Mock(); + var altinnAppModelMock = new Mock(); var appResourcesMock = new Mock(); var prefillMock = new Mock(); var registerMock = new Mock(); var pdpMock = new Mock(); ILogger logger = new NullLogger(); - var statelessDataController = new StatelessDataController(logger, altinnAppMock.Object, appResourcesMock.Object, + var statelessDataController = new StatelessDataController(logger, altinnAppMock.Object, altinnAppModelMock.Object, appResourcesMock.Object, prefillMock.Object, registerMock.Object, pdpMock.Object); // Act @@ -56,13 +53,14 @@ public async void Get_Returns_BadRequest_when_appResource_classRef_is_null() { // Arrange var altinnAppMock = new Mock(); + var appModelMock = new Mock(); var appResourcesMock = new Mock(); var prefillMock = new Mock(); var registerMock = new Mock(); var pdpMock = new Mock(); var dataType = "some-value"; ILogger logger = new NullLogger(); - var statelessDataController = new StatelessDataController(logger, altinnAppMock.Object, appResourcesMock.Object, + var statelessDataController = new StatelessDataController(logger, altinnAppMock.Object, appModelMock.Object, appResourcesMock.Object, prefillMock.Object, registerMock.Object, pdpMock.Object); @@ -86,13 +84,14 @@ public async void Get_Returns_BadRequest_when_party_header_count_greater_than_on { // Arrange var altinnAppMock = new Mock(); + var appModelMock = new Mock(); var appResourcesMock = new Mock(); var prefillMock = new Mock(); var registerMock = new Mock(); var pdpMock = new Mock(); var dataType = "some-value"; ILogger logger = new NullLogger(); - var statelessDataController = new StatelessDataController(logger, altinnAppMock.Object, appResourcesMock.Object, + var statelessDataController = new StatelessDataController(logger, altinnAppMock.Object, appModelMock.Object, appResourcesMock.Object, prefillMock.Object, registerMock.Object, pdpMock.Object); statelessDataController.ControllerContext = new ControllerContext(); statelessDataController.ControllerContext.HttpContext = new DefaultHttpContext(); @@ -118,13 +117,14 @@ public async void Get_Returns_BadRequest_when_instance_owner_is_empty_party_head { // Arrange var altinnAppMock = new Mock(); + var appModelMock = new Mock(); var appResourcesMock = new Mock(); var prefillMock = new Mock(); var registerMock = new Mock(); var pdpMock = new Mock(); var dataType = "some-value"; ILogger logger = new NullLogger(); - var statelessDataController = new StatelessDataController(logger, altinnAppMock.Object, appResourcesMock.Object, + var statelessDataController = new StatelessDataController(logger, altinnAppMock.Object, appModelMock.Object, appResourcesMock.Object, prefillMock.Object, registerMock.Object, pdpMock.Object); statelessDataController.ControllerContext = new ControllerContext(); statelessDataController.ControllerContext.HttpContext = new DefaultHttpContext(); @@ -150,13 +150,14 @@ public async void Get_Returns_BadRequest_when_instance_owner_is_empty_user_in_co { // Arrange var altinnAppMock = new Mock(); + var appModelMock = new Mock(); var appResourcesMock = new Mock(); var prefillMock = new Mock(); var registerMock = new Mock(); var pdpMock = new Mock(); var dataType = "some-value"; ILogger logger = new NullLogger(); - var statelessDataController = new StatelessDataController(logger, altinnAppMock.Object, appResourcesMock.Object, + var statelessDataController = new StatelessDataController(logger, altinnAppMock.Object, appModelMock.Object, appResourcesMock.Object, prefillMock.Object, registerMock.Object, pdpMock.Object); statelessDataController.ControllerContext = new ControllerContext(); statelessDataController.ControllerContext.HttpContext = new DefaultHttpContext(); @@ -189,13 +190,14 @@ public async void Get_Returns_Forbidden_when_returned_descision_is_Deny() { // Arrange var altinnAppMock = new Mock(); + var appModelMock = new Mock(); var appResourcesMock = new Mock(); var prefillMock = new Mock(); var registerMock = new Mock(); var pdpMock = new Mock(); var dataType = "some-value"; ILogger logger = new NullLogger(); - var statelessDataController = new StatelessDataController(logger, altinnAppMock.Object, appResourcesMock.Object, + var statelessDataController = new StatelessDataController(logger, altinnAppMock.Object, appModelMock.Object, appResourcesMock.Object, prefillMock.Object, registerMock.Object, pdpMock.Object); statelessDataController.ControllerContext = new ControllerContext(); statelessDataController.ControllerContext.HttpContext = new DefaultHttpContext(); @@ -240,6 +242,7 @@ public async void Get_Returns_OK_with_appModel() { // Arrange var altinnAppMock = new Mock(); + var appModelMock = new Mock(); var appResourcesMock = new Mock(); var prefillMock = new Mock(); var registerMock = new Mock(); @@ -247,7 +250,7 @@ public async void Get_Returns_OK_with_appModel() var dataType = "some-value"; var classRef = typeof(DummyModel).FullName; ILogger logger = new NullLogger(); - var statelessDataController = new StatelessDataController(logger, altinnAppMock.Object, appResourcesMock.Object, + var statelessDataController = new StatelessDataController(logger, altinnAppMock.Object, appModelMock.Object, appResourcesMock.Object, prefillMock.Object, registerMock.Object, pdpMock.Object); statelessDataController.ControllerContext = new ControllerContext(); statelessDataController.ControllerContext.HttpContext = new DefaultHttpContext(); @@ -271,7 +274,7 @@ public async void Get_Returns_OK_with_appModel() } } }); - altinnAppMock.Setup(a => a.CreateNewAppModel(classRef)) + appModelMock.Setup(a => a.Create(classRef)) .Returns(new DummyModel()); // Act @@ -283,7 +286,7 @@ public async void Get_Returns_OK_with_appModel() result.Should().BeOfType().Which.Value.Should().BeOfType(); appResourcesMock.Verify(x => x.GetClassRefForLogicDataType(dataType), Times.Once); pdpMock.Verify(p => p.GetDecisionForRequest(It.IsAny())); - altinnAppMock.Verify(a => a.CreateNewAppModel(classRef), Times.Once); + appModelMock.Verify(a => a.Create(classRef), Times.Once); prefillMock.Verify(p => p.PrefillDataModel("12345", dataType, It.IsAny(), null)); altinnAppMock.Verify(a => a.RunProcessDataRead(It.IsAny(), null, It.IsAny())); appResourcesMock.VerifyNoOtherCalls(); diff --git a/test/Altinn.App.Api.Tests/Controllers/StatelessPagesControllerTests.cs b/test/Altinn.App.Api.Tests/Controllers/StatelessPagesControllerTests.cs index d1283ecea..9c34f32bf 100644 --- a/test/Altinn.App.Api.Tests/Controllers/StatelessPagesControllerTests.cs +++ b/test/Altinn.App.Api.Tests/Controllers/StatelessPagesControllerTests.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Altinn.App.Api.Controllers; using Altinn.App.Api.Tests.Controllers.TestResources; +using Altinn.App.Core.Interface; using Altinn.App.PlatformServices.Models; using Altinn.App.Services.Interface; using FluentAssertions; @@ -26,14 +27,14 @@ public async Task GetPageOrder_Returns_page_order_from_IPageOrder() // Arrange var appResourcesMock = new Mock(); var pageOrderMock = new Mock(); - var altinnAppMock = new Mock(); + var appModelMock = new Mock(); const string dataTypeId = "DummyModel"; string classRef = typeof(DummyModel).FullName; List expected = new List { "pagetwo", "pagethree" }; Type type = typeof(DummyModel); appResourcesMock.Setup(r => r.GetClassRefForLogicDataType(dataTypeId)) .Returns(classRef); - altinnAppMock.Setup(a => a.GetAppModelType(classRef)).Returns(type); + appModelMock.Setup(a => a.GetModelType(classRef)).Returns(type); pageOrderMock.Setup(p => p.GetPageOrder( It.IsAny(), @@ -57,7 +58,7 @@ public async Task GetPageOrder_Returns_page_order_from_IPageOrder() )); var controller = new StatelessPagesController( - altinnAppMock.Object, + appModelMock.Object, appResourcesMock.Object, pageOrderMock.Object); @@ -75,9 +76,9 @@ public async Task GetPageOrder_Returns_BadRequest_when_datatype_null() // Arrange var appResourcesMock = new Mock(); var pageOrderMock = new Mock(); - var altinnAppMock = new Mock(); + var appModelMock = new Mock(); var controller = new StatelessPagesController( - altinnAppMock.Object, + appModelMock.Object, appResourcesMock.Object, pageOrderMock.Object ); @@ -89,7 +90,7 @@ public async Task GetPageOrder_Returns_BadRequest_when_datatype_null() response.Result.Should().BeOfType(); appResourcesMock.VerifyNoOtherCalls(); pageOrderMock.VerifyNoOtherCalls(); - altinnAppMock.VerifyNoOtherCalls(); + appModelMock.VerifyNoOtherCalls(); } [Fact] @@ -98,9 +99,9 @@ public async Task GetPageOrder_Returns_BadRequest_when_datatype_empty() // Arrange var appResourcesMock = new Mock(); var pageOrderMock = new Mock(); - var altinnAppMock = new Mock(); + var appModelMock = new Mock(); var controller = new StatelessPagesController( - altinnAppMock.Object, + appModelMock.Object, appResourcesMock.Object, pageOrderMock.Object ); @@ -112,6 +113,6 @@ public async Task GetPageOrder_Returns_BadRequest_when_datatype_empty() response.Result.Should().BeOfType(); appResourcesMock.VerifyNoOtherCalls(); pageOrderMock.VerifyNoOtherCalls(); - altinnAppMock.VerifyNoOtherCalls(); + appModelMock.VerifyNoOtherCalls(); } } \ No newline at end of file diff --git a/test/Altinn.App.Core.Tests/Implementation/DefaultPageOrderTest.cs b/test/Altinn.App.Core.Tests/Implementation/DefaultPageOrderTest.cs index ca7efe376..40cfcaedc 100644 --- a/test/Altinn.App.Core.Tests/Implementation/DefaultPageOrderTest.cs +++ b/test/Altinn.App.Core.Tests/Implementation/DefaultPageOrderTest.cs @@ -22,34 +22,7 @@ public DefaultPageOrderTest() } [Fact] - public async Task GetPageOrder_Returns_PageOrder_From_IAltinnApp() - { - // Arrange - AppIdentifier appIdentifier = new AppIdentifier("ttd", "best-app"); - Guid guid = Guid.NewGuid(); - InstanceIdentifier instanceIdentifier = new InstanceIdentifier(1337, guid); - string layoutSetId = "layoutSetId"; - string currentPage = "currentPage"; - string dataTypeId = "dataTypeId"; - object formData = new object(); - - List expected = new List { "page1", "page2" }; - altinnAppMock.Setup(aa => aa.GetPageOrder("ttd", "best-app", 1337, guid, layoutSetId, currentPage, dataTypeId, formData)).Returns(Task.FromResult(expected)); - - // Act - DefaultPageOrder target = new DefaultPageOrder(altinnAppMock.Object, appResourcesMock.Object); - - List actual = await target.GetPageOrder(appIdentifier, instanceIdentifier, layoutSetId, currentPage, dataTypeId, formData); - - // Assert - Assert.Equal(expected, actual); - altinnAppMock.Verify(aa => aa.GetPageOrder("ttd", "best-app", 1337, guid, layoutSetId, currentPage, dataTypeId, formData), Times.Once); - appResourcesMock.VerifyNoOtherCalls(); - altinnAppMock.VerifyNoOtherCalls(); - } - - [Fact] - public async Task GetPageOrder_Returns_LayoutSettingsForSet_when_NoInstance_and_layoutSetId_is_defined() + public async Task GetPageOrder_Returns_LayoutSettingsForSet_when_layoutSetId_is_defined() { // Arrange AppIdentifier appIdentifier = new AppIdentifier("ttd", "best-app"); @@ -76,7 +49,7 @@ public async Task GetPageOrder_Returns_LayoutSettingsForSet_when_NoInstance_and_ } [Fact] - public async Task GetPageOrder_Returns_LayoutSettings_when_NoInstance_and_layoutSetId_is_null() + public async Task GetPageOrder_Returns_LayoutSettings_layoutSetId_is_null() { // Arrange AppIdentifier appIdentifier = new AppIdentifier("ttd", "best-app"); diff --git a/test/Altinn.App.Core.Tests/Implementation/NullDataProcessorTests.cs b/test/Altinn.App.Core.Tests/Implementation/NullDataProcessorTests.cs new file mode 100644 index 000000000..dc3d62566 --- /dev/null +++ b/test/Altinn.App.Core.Tests/Implementation/NullDataProcessorTests.cs @@ -0,0 +1,54 @@ +using Altinn.App.Core.Implementation; +using Altinn.App.PlatformServices.Tests.Implementation.TestResources; +using Altinn.Platform.Storage.Interface.Models; +using FluentAssertions; +using Xunit; + +namespace Altinn.App.PlatformServices.Tests.Implementation; + +public class NullDataProcessorTests +{ + [Fact] + public async void NullDataProcessor_ProcessDataRead_makes_no_changes_and_returns_false() + { + // Arrange + var dataProcessor = new NullDataProcessor(); + DummyModel expected = new DummyModel() + { + Name = "Test" + }; + object input = new DummyModel() + { + Name = "Test" + }; + + // Act + var result = await dataProcessor.ProcessDataRead(new Instance(), null, input); + + // Assert + result.Should().BeFalse(); + input.Should().BeEquivalentTo(expected); + } + + [Fact] + public async void NullDataProcessor_ProcessDataWrite_makes_no_changes_and_returns_false() + { + // Arrange + var dataProcessor = new NullDataProcessor(); + DummyModel expected = new DummyModel() + { + Name = "Test" + }; + object input = new DummyModel() + { + Name = "Test" + }; + + // Act + var result = await dataProcessor.ProcessDataWrite(new Instance(), null, input); + + // Assert + result.Should().BeFalse(); + input.Should().BeEquivalentTo(expected); + } +} \ No newline at end of file diff --git a/test/Altinn.App.Core.Tests/Implementation/NullInstanceValidatorTests.cs b/test/Altinn.App.Core.Tests/Implementation/NullInstanceValidatorTests.cs new file mode 100644 index 000000000..f45ac27b4 --- /dev/null +++ b/test/Altinn.App.Core.Tests/Implementation/NullInstanceValidatorTests.cs @@ -0,0 +1,38 @@ +using Altinn.App.Core.Implementation; +using Altinn.App.PlatformServices.Tests.Implementation.TestResources; +using Altinn.Platform.Storage.Interface.Models; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Xunit; + +namespace Altinn.App.PlatformServices.Tests.Implementation; + +public class NullInstanceValidatorTests +{ + [Fact] + public async void NullInstanceValidator_ValidateData_does_not_add_to_ValidationResults() + { + // Arrange + var instanceValidator = new NullInstanceValidator(); + ModelStateDictionary validationResults = new ModelStateDictionary(); + + // Act + await instanceValidator.ValidateData(new DummyModel(), validationResults); + + // Assert + Assert.Empty(validationResults); + } + + [Fact] + public async void NullInstanceValidator_ValidateTask_does_not_add_to_ValidationResults() + { + // Arrange + var instanceValidator = new NullInstanceValidator(); + ModelStateDictionary validationResults = new ModelStateDictionary(); + + // Act + await instanceValidator.ValidateTask(new Instance(), "task0", validationResults); + + // Assert + Assert.Empty(validationResults); + } +} \ No newline at end of file diff --git a/test/Altinn.App.Core.Tests/Implementation/NullInstantiationTests.cs b/test/Altinn.App.Core.Tests/Implementation/NullInstantiationTests.cs new file mode 100644 index 000000000..c8094de03 --- /dev/null +++ b/test/Altinn.App.Core.Tests/Implementation/NullInstantiationTests.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using Altinn.App.Core.Implementation; +using Altinn.App.PlatformServices.Tests.Implementation.TestResources; +using Altinn.Platform.Storage.Interface.Models; +using FluentAssertions; +using Xunit; + +namespace Altinn.App.PlatformServices.Tests.Implementation; + +public class NullInstantiationTests +{ + [Fact] + public async void NullInstantiationTest_Validation_returns_null() + { + // Arrange + var nullInstantiation = new NullInstantiation(); + + // Act + var result = await nullInstantiation.Validation(new Instance()); + + // Assert + result.Should().BeNull(); + } + + [Fact] + public async void NullInstantiationTest_DataCreation_changes_nothing() + { + // Arrange + var nullInstantiation = new NullInstantiation(); + DummyModel expected = new DummyModel() + { + Name = "Test", + }; + object input = new DummyModel() + { + Name = "Test" + }; + + // Act + await nullInstantiation.DataCreation(new Instance(), input, new Dictionary()); + + // Assert + input.Should().BeEquivalentTo(expected); + } +} \ No newline at end of file diff --git a/test/Altinn.App.Core.Tests/Implementation/NullPdfFormatterTests.cs b/test/Altinn.App.Core.Tests/Implementation/NullPdfFormatterTests.cs new file mode 100644 index 000000000..3049c59f9 --- /dev/null +++ b/test/Altinn.App.Core.Tests/Implementation/NullPdfFormatterTests.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using Altinn.App.Common.Models; +using Altinn.App.PlatformServices.Implementation; +using Altinn.App.PlatformServices.Tests.Implementation.TestResources; +using FluentAssertions; +using Xunit; + +namespace Altinn.App.PlatformServices.Tests.Implementation; + +public class NullPdfFormatterTests +{ + [Fact] + public async void NullPdfFormatter_FormatPdf_returns_Layoutsettings_as_is() + { + // Arrange + var layoutSettings = new LayoutSettings() + { + Components = new Components() + { + ExcludeFromPdf = new List() + { + "excludeFromPdf" + } + }, + Pages = new Pages() + { + Order = new List() + { + "Page1", + "PageExcludeFromPdf" + }, + ExcludeFromPdf = new List() + { + "PageExcludeFromPdf" + } + } + }; + + var nullPdfFormatter = new NullPdfFormatter(); + + // Act + var result = await nullPdfFormatter.FormatPdf(layoutSettings, new DummyModel()); + + // Assert + result.Should().BeEquivalentTo(layoutSettings); + } +} \ No newline at end of file diff --git a/test/Altinn.App.Core.Tests/Implementation/NullTaskProcessorTests.cs b/test/Altinn.App.Core.Tests/Implementation/NullTaskProcessorTests.cs new file mode 100644 index 000000000..8bc32826c --- /dev/null +++ b/test/Altinn.App.Core.Tests/Implementation/NullTaskProcessorTests.cs @@ -0,0 +1,24 @@ +using Altinn.App.Core.Implementation; +using Altinn.Platform.Storage.Interface.Models; +using FluentAssertions; +using Xunit; + +namespace Altinn.App.PlatformServices.Tests.Implementation; + +public class NullTaskProcessorTests +{ + [Fact] + public async void ProcessTaskEnd_should_do_nothing() + { + // Arrange + var taskProcessor = new NullTaskProcessor(); + Instance expected = new Instance(); + Instance input = new Instance(); + + // Act + await taskProcessor.ProcessTaskEnd("123", input); + + // Assert + input.Should().BeEquivalentTo(expected); + } +} \ No newline at end of file diff --git a/test/Altinn.App.Core.Tests/Implementation/TestResources/DummyModel.cs b/test/Altinn.App.Core.Tests/Implementation/TestResources/DummyModel.cs new file mode 100644 index 000000000..fc49beabe --- /dev/null +++ b/test/Altinn.App.Core.Tests/Implementation/TestResources/DummyModel.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; +using System.Xml.Serialization; +using Newtonsoft.Json; + +namespace Altinn.App.PlatformServices.Tests.Implementation.TestResources; + +public class DummyModel +{ + [XmlElement("name", Order = 1)] + [JsonProperty("name")] + [JsonPropertyName("name")] + public string Name { get; set; } +} \ No newline at end of file diff --git a/v7-changelog.md b/v7-changelog.md index a66238623..f9cf30ac2 100644 --- a/v7-changelog.md +++ b/v7-changelog.md @@ -5,4 +5,12 @@ - Removed support for .Net5.0 3. Moved and grouped http clients into new namespaces - From Implementation folder to Altinn.App.Core.Infrastructure.Clients.[Area] where area is Register, Storage - - Not named HttpClients since clients might be other than http. \ No newline at end of file + - Not named HttpClients since clients might be other than http. +4. Replaced virtual/abstract methods in AppBase with Dependency Injection to implement custom code. + - GetAppModelType() and CreateNewAppModel() is replaced by new class in the App that implements IAppModel (This file should be the only dotnet code needed by default for an app) + - Overriding ProcessDataWrite/Read is now done by injecting a class implementing IDataProcessor. `App/logic/DataProcessing/DataProcessingHandler.cs` in app-template is no longer needed + - Overriding RunInstantiationValidation and DataCreation is now done by injecting a class implementing IInstantiation. `App/logic/InstantiationHandler.cs` in app-template is no longer needed. + - Overriding validation logic is done by injecting a class implementing IInstanceValidation. `App/logic/Validation/ValidationHandler.cs` in app-template is no longer needed. + - ICustomPdfGenerator renamed to IPdfFormatter, ICustomPdfGenerator was already implemented with DI, `App/logic/Print/PdfHandler.cs` in app-template is no longer needed. + - Deprecated method `IAltinnApp.GetPageOrder()` is removed. It's now only possible to override this logic by injecting a class implementing IPageOrder + - Overriding logic for RunProcessTaskEnd is done by injecting a class implementing ITaskProcessor. \ No newline at end of file