Breaking changes
Overview of breaking changes introduced in v7.0.0 of the App Nuget packages.
Updating Altinn.App.* PackageReferences from v6 to v7
As mentioned here we have moved the code around and reduced the number of nugets to two.
To upgrade from v6 nuget to v7 nugets the Altinn.App.* PackageReferences in App/App.csproj
needs to be changed from:
<ItemGroup>
<PackageReference Include="Altinn.App.Api" Version="6.0.2">
<CopyToOutputDirectory>lib\$(TargetFramework)\*.xml</CopyToOutputDirectory>
</PackageReference>
<PackageReference Include="Altinn.App.Common" Version="6.0.2" />
<PackageReference Include="Altinn.App.PlatformServices" Version="6.0.2" />
<!-- Additional PackageReferences -->
</ItemGroup>
to
<ItemGroup>
<PackageReference Include="Altinn.App.Api" Version="7.0.0">
<CopyToOutputDirectory>lib\$(TargetFramework)\*.xml</CopyToOutputDirectory>
</PackageReference>
<PackageReference Include="Altinn.App.Core" Version="7.0.0" />
<!-- Additional PackageReferences -->
</ItemGroup>
Once you or your IDE has completed the dotnet restore
process your code will have multiple compilation warnings.
Work your way through the rest of this page and you will remove them one at a time.
Upgrading Program.cs
It’s highly recommended to follow the migration guide to v6 if your application still hasn’t been upgraded to from v4 or v5.
In previous versions Altinn provided code and registration of custom/override services where hard to separate. As an attempt to make it easier to see customer provided services we have extracted most of our code and introduced a new method at the top of Program.cs
Steps:
- Identify the custom services your application has registered in Program.cs. Take a note of these as we will have to register them again later.
- Copy and paste the version 7 of Program.cs from our template at altinn/app-template-dotnet into your
App/Program.cs
(replacing existing code) - Register your apps custom services from step 1 in the method
RegisterCustomAppServices
Replacing or upgrading old classes to implement new Interfaces
Upgrade and register DataProcessingHandler
Custom logic while performing DataWrite or DataRead are from v7 handled by registering a service class
implementing Altinn.App.Core.Features.DataProcessing.IDataProcessor
If your App/logic/DataProcessing/DataProcessingHandler.cs
is similar to the following code you can delete the file and
move
on Replace InstantiationHandler.cs and register new service.
using System;
using System.Threading.Tasks;
using Altinn.Platform.Storage.Interface.Models;
namespace Altinn.App.AppLogic.DataProcessing
{
public class DataProcessingHandler
{
public async Task<bool> ProcessDataRead(Instance instance, Guid? dataId, object data)
{
return await Task.FromResult(false);
}
public async Task<bool> ProcessDataWrite(Instance instance, Guid? dataId, object data)
{
return await Task.FromResult(false);
}
}
}
If you have custom code in this class complete the steps below:
- Create a new class named
DataProcessor
in the folderApp/logic/DataProcessing
, make the class implement the interfaceAltinn.App.Core.Features.IDataProcessor
. The file should now look something like this:Note that you might have to add the async keyword if they aren’t added by default. You only need to add them if you do async calls within your code.using System; using System.Threading.Tasks; using Altinn.App.Core.Features; using Altinn.Platform.Storage.Interface.Models; namespace Altinn.App.AppLogic.DataProcessing; public class DataProcessor : IDataProcessor { public async Task<bool> ProcessDataRead(Instance instance, Guid? dataId, object data) { } public async Task<bool> ProcessDataWrite(Instance instance, Guid? dataId, object data) { } }
- Move your code from the file
App/logic/DataProcessing/DataProcessingHandler.cs
to the file you just created. - Register
DataProcessor
in the methodRegisterCustomAppServices
App/Program.cs
Remember to add the necessary usages.void RegisterCustomAppServices(IServiceCollection services, IConfiguration config) { services.AddTransient<IDataProcessor, DataProcessor>(); // Other custom services }
- Delete the old DataProcessingHandler.cs file
Replace InstantiationHandler.cs and register new service
The logic that previously was defined in App/logic/InstantiationHandler.cs
is now defined by implementing two interfaces
IInstantiationProcessor
is used to do custom data/instance manipulation during instantiation. Replaces the methodDataCreation
inInstantiationHandler
IInstantiationValidator
is used to do custom validation during Instantiation. Replaces the methodRunInstantiationValidation
inInstantiationHandler
If your App/logic/InstantiationHandler.cs
looks like the code below you have no custom code here and can delete the class now and move on to Upgrade ValidationHandler.cs and register new service
using System.Collections.Generic;
using System.Threading.Tasks;
using Altinn.App.Services.Interface;
using Altinn.App.Services.Models.Validation;
using Altinn.Platform.Storage.Interface.Models;
namespace Altinn.App.AppLogic
{
public class InstantiationHandler
{
private IProfile _profileService;
private IRegister _registerService;
public InstantiationHandler(IProfile profileService, IRegister registerService)
{
_profileService = profileService;
_registerService = registerService;
}
public async Task<InstantiationValidationResult> RunInstantiationValidation(Instance instance)
{
return await Task.FromResult((InstantiationValidationResult)null);
}
public async Task DataCreation(Instance instance, object data, Dictionary<string, string> prefill)
{
await Task.CompletedTask;
}
}
}
If you have custom code in the method DataCreation
complete these steps:
- Create a new Class named
InstantiationProcessor.cs
inApp/logic/DataProcessing
and implement the interfaceAltinn.App.Core.Features.IInstantiationProcessor
. The class can be named or placed where you like, this is only a suggestion. The file should look something like this now:using System.Collections.Generic; using System.Threading.Tasks; using Altinn.App.Core.Features; using Altinn.Platform.Storage.Interface.Models; namespace Altinn.App.AppLogic.DataProcessing; public class InstantiationProcessor : IInstantiationProcessor { public async Task DataCreation(Instance instance, object data, Dictionary<string, string> prefill) { } }
- Move all the code from
DataCreation
inÌnstantiationHandler.cs
into the methodDataCreation
in the class you just created. - Register
InstantiationProcessor
in the methodRegisterCustomAppServices
App/Program.cs
Remember to add the necessary usages.void RegisterCustomAppServices(IServiceCollection services, IConfiguration config) { services.AddTransient<IInstantiationProcessor, InstantiationProcessor>(); // Other custom services }
If you have custom code in the method RunInstantiationValidation
complete the steps below, if not delete App/logic/InstantiationHandler.cs
and move on to Upgrade ValidationHandler.cs and register new service:
- Create a new Class named
InstantiationValidator.cs
inApp/logic/Validation
and implement the interfaceAltinn.App.Core.Features.IInstantiationValidator
. The class can be named or placed where you like, this is only a suggestion. The file should look something like this now:using System.Threading.Tasks; using Altinn.App.Core.Features; using Altinn.App.Core.Models.Validation; using Altinn.Platform.Storage.Interface.Models; namespace Altinn.App.AppLogic.Validation; public class InstantiationValidator : IInstantiationValidator { public async Task<InstantiationValidationResult> Validate(Instance instance) { } }
- Move all the code from
RunInstantiationValidation
inÌnstantiationHandler.cs
into the methodValidate
in the class you just created. - Register
InstantiationValidator
in the methodRegisterCustomAppServices
App/Program.cs
Remember to add the necessary usages.void RegisterCustomAppServices(IServiceCollection services, IConfiguration config) { services.AddTransient<IInstantiationValidator, InstantiationValidator>(); // Other custom services }
You can now delete the file App/logic/InstantiationHandler.cs
Upgrade ValidationHandler.cs and register new service
Custom data and task validation is in v7 handled by registering a service class
implementing Altinn.App.Core.Features.IInstanceValidator
.
If your App/logic/Validation/ValidationHandler.cs
looks like the code below you have no custom code and can delete the
file. And move on to Upgrading custom PdfFormatting.
using System.Threading.Tasks;
using Altinn.Platform.Storage.Interface.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Altinn.App.AppLogic.Validation
{
public class ValidationHandler
{
private IHttpContextAccessor _httpContextAccessor;
public ValidationHandler(IHttpContextAccessor httpContextAccessor = null)
{
_httpContextAccessor = httpContextAccessor;
}
public async Task ValidateData(object data, ModelStateDictionary validationResults)
{
await Task.CompletedTask;
}
public async Task ValidateTask(Instance instance, string taskId, ModelStateDictionary validationResults)
{
await Task.CompletedTask;
}
}
}
If you have custom code here complete the steps below:
- Create a new class named
InstanceValidator
in the folderApp/logic/Validation
, make the class implement the interfaceAltinn.App.Core.Features.IInstanceValidator
. The file should now look something like this:using System.Threading.Tasks; using Altinn.App.Core.Features; using Altinn.Platform.Storage.Interface.Models; using Microsoft.AspNetCore.Mvc.ModelBinding; namespace Altinn.App.AppLogic.Validation; public class InstanceValidator : IInstanceValidator { public async Task ValidateData(object data, ModelStateDictionary validationResults) { } public async Task ValidateTask(Instance instance, string taskId, ModelStateDictionary validationResults) { } }
- Move the code in
ValidateData
method in the old classValidatorHandler
to the new one inInstanceValidator
class. - Move the code in
ValidateTask
method in the old classValidatorHandler
to the new one inInstanceValidator
class. - Register
InstanceValidator
in the methodRegisterCustomAppServices
App/Program.cs
Remember to add the necessary usages.void RegisterCustomAppServices(IServiceCollection services, IConfiguration config) { services.AddTransient<IInstanceValidator, InstanceValidator>(); // Other custom services }
- Delete the ValidationHandler.cs file.
Upgrading Custom PDF formatting logic
In previous versions dynamic customization was handled by registering a service implementing ICustomPdfHandler. In v7
this is done by registering a service implementing Altinn.App.Core.Features.Pdf.IPdfFormatter
If your App/logic/Pdf/PdfHandler.cs
looks like the following code you have no custom code and can delete the file and
move on to Updating PageOrder logic
using System.Threading.Tasks;
using Altinn.App.Common.Models;
using Altinn.App.PlatformServices.Interface;
namespace Altinn.App.AppLogic.Print
{
public class PdfHandler: ICustomPdfHandler
{
public async Task<LayoutSettings> FormatPdf(LayoutSettings layoutSettings, object data)
{
return await Task.FromResult(layoutSettings);
}
}
}
If not, complete the steps below:
- Create a new class named
PdfFormatter
in the folderApp/logic/Pdf
. make the class implement the interfaceAltinn.App.Core.Features.IPdfFormatter
. The file should now look something like this:using System.Threading.Tasks; using Altinn.App.Core.Features; using Altinn.App.Core.Models; namespace Altinn.App.AppLogic.Pdf { public class PdfFormatter: IPdfFormatter { public async Task<LayoutSettings> FormatPdf(LayoutSettings layoutSettings, object data) { } } }
- Move your code from ICustomPdfHandler to the newly created PdfFormatter class.
- Register
PdfFormatter
in the methodRegisterCustomAppServices
App/Program.cs
Remember to add the necessary usages.void RegisterCustomAppServices(IServiceCollection services, IConfiguration config) { services.AddTransient<IPdfFormatter, PdfFormatter>(); // Other custom services }
- Delete the old file
- Remove the transient service registration of the old class from
Program.cs
. Should look something like this:services.AddTransient<ICustomPdfHandler, PdfHandler>();
(remove it by deleting the line)
Upgrading PageOrder.cs
In v5 of the nugets tracks or page order was extracted in a non-breaking way from App.cs. The old way was deprecated in v6 and now removed in v7.
The new way of defining page order in v5 has not really changed in v7, but some interfaces has moved namespace.
If you don’t have any classes implementing IPageOrder you can safely move on to Moving code form App.cs and removing it
If you do have a class implementing IPageOrder here are some changes in namespace that might be helpful to know:
- The namespace for IPageOrder has changed from Altinn.App.Services.Interfaces to
Altinn.App.Core.Features.PageOrder
- The namespace for AppIdentifier has changed from Altinn.App.PlatformSerices.Models to
Altinn.App.Core.Models
- The namespace for IAppResources has changed from Altinn.App.PlatformSerices.Interface
Altinn.App.Core.Interface
Moving eFormidling code
This next chapter only applies to applications which integrates with eFormidling. If yours don’t you can move on to Moving code from App.cs and removing it
To determine if your app is integrated against eFormidling check if you have defined the
method GenerateEFormidlingMetadata
defined in App/logic/App.cs
Move code from GenerateEFormidlingMetadata in App.cs
- Create a new class named
EFormidlingMetadata
in the folderApp/logic/EFormidling
. You can name and place the file whatever and wherever you like, this is just an suggestion. Make the newly created class implement the interfaceAltinn.App.Core.EFormidling.Interface.IEFormidlingMetadata
. The file should look something like this now:using System.IO; using System.Threading.Tasks; using Altinn.App.Core.EFormidling.Interface; using Altinn.Platform.Storage.Interface.Models; namespace Altinn.App.logic.EFormidling; public class EFormidlingMetadata : IEFormidlingMetadata { public Task<(string MetadataFilename, Stream Metadata)> GenerateEFormidlingMetadata(Instance instance) { } }
- Move the code from the method
GenerateEFormidlingMetadata
inApp/logic/App.cs
to the methodGenerateEFormidlingMetadata
in the class you just created.
Move code from GetEFormidlingReceivers in App.cs
If you have no definition of GetEFormidlingReceivers
in App/logic/App.cs
you can move on to Register Eformidling implementation. If it is defined complete the following steps
- Create a new class named
EFormidlingReceivers
inApp/logic/EFormidling
. You can name and place the file whatever and wherever you like, this is just an suggestion. Make the new class implement the interfaceAltinn.App.Core.EFormidling.Interface.IEFormidlingReceivers
. The file should look something like this now:using System.Collections.Generic; using System.Threading.Tasks; using Altinn.App.Core.EFormidling.Interface; using Altinn.Common.EFormidlingClient.Models.SBD; using Altinn.Platform.Storage.Interface.Models; namespace Altinn.App.logic.EFormidling; public class EFormidlingReceivers : IEFormidlingReceivers { public Task<List<Receiver>> GetEFormidlingReceivers(Instance instance) { } }
- Move the code from the method
GetEFormidlingReceivers
inApp/logic/App.cs
to the mwthodGetEformidlingReceivers
in the class you just created.
Register Eformidling implementation
This step depends on the previous steps. Whether your code has custom logic for GetEFormidlingReceivers or not.
In the method RegisterCustomAppServices
in Program.cs
you need to register the eFormidling implementation.
To add eFormidling services and your implementation EFormidlingMetadata
add this to your RegisterCustomAppServices
.
void RegisterCustomAppServices(IServiceCollection services, IConfiguration config)
{
services.AddEFormidlingServices<EFormidlingMetadata>(config);
}
This will register the default implementation of IEFormidlingReceivers
If you have a custom implementation of IEFormidlingReceivers
you should add your implementation as the second type parameter:
void RegisterCustomAppServices(IServiceCollection services, IConfiguration config)
{
services.AddEFormidlingServices<EFormidlingMetadata, EFormidlingReceivers>(config);
}
Moving code from App.cs and removing it
As we are going to extract code from App.cs and eventually removing it there is no need to pay attention to compilation error.
The next sections will extract custom code from App.cs, if any, and move it to its new home.
Moving custom code in RunProcessDataRead and RunProcessDataWrite
If RunProcessDataRead
and RunProcessDataWrite
in App/App.cs
looks like the code below you have no custom code and can safely move on to Moving custom code in RunDataValidation and RunTaskValidation. If not complete the steps below.
public override async Task<bool> RunProcessDataRead(Instance instance, Guid? dataId, object data)
{
return await _dataProcessingHandler.ProcessDataRead(instance, dataId, data);
}
public override async Task<bool> RunProcessDataWrite(Instance instance, Guid? dataId, object data)
{
return await _dataProcessingHandler.ProcessDataWrite(instance, dataId, data);
}
- Move custom code in
RunProcessDataRead
to the methodProcessDataRead
inApp/logic/DataProcessing/DataProcessor.cs
that was created and registered in the step Upgrade and register DataProcessingHadler - Move custom code in
RunProcessDataWrite
to the methodProcessDataWrite
inApp/logic/DataProcessing/DataProcessor.cs
Moving custom code in RunDataValidation and RunTaskValidation
If RunDataValidation
and RunTaskValidation
looks like the code below you have no custom code and can safely move on to Moving custom code in RunInstantiationValidation and RunDataCreation. If not complete the steps below
public override async Task RunDataValidation(object data, ModelStateDictionary validationResults)
{
await _validationHandler.ValidateData(data, validationResults);
}
public override async Task RunTaskValidation(Instance instance, string taskId, ModelStateDictionary validationResults)
{
await _validationHandler.ValidateTask(instance, taskId, validationResults);
}
- Move custom code in
RunDataValidation
to the methodValidateData
inApp/logic/Validation/InstanceValidator.cs
that you created and registered here Upgrade ValidationHandler.cs and register new service. - Move custom code in
RunTaskValidation
to the methodValidateTask
inApp/logic/Validation/InstanceValidator.cs
Moving custom code in RunDataCreation
If RunDataCreation
looks like the code below you have no custom code and can safely move on to Moving custom code in RunInstantiationValidation. If not complete the steps below.
public override async Task RunDataCreation(Instance instance, object data, Dictionary<string, string> prefill)
{
await _instantiationHandler.DataCreation(instance, data, prefill);
}
- If you haven’t already: Create a new class named
InstantiationProcessor.cs
inApp/logic/DataProcessing
and implement the interfaceAltinn.App.Core.Features.IInstantiationProcessor
. The class can be named or placed where you like, this is only a suggestion. The file should look something like this now:using System.Collections.Generic; using System.Threading.Tasks; using Altinn.App.Core.Features; using Altinn.Platform.Storage.Interface.Models; namespace Altinn.App.AppLogic.DataProcessing; public class InstantiationProcessor : IInstantiationProcessor { public async Task DataCreation(Instance instance, object data, Dictionary<string, string> prefill) { } }
- Move custom code in
DataCreation
to the methodDataCreation
inApp/logic/DataProcessing/InstantiationHandler.cs
that you created and registered here Replace InstantiationHandler.cs and register new service - Register
InstantiationProcessor
in the methodRegisterCustomAppServices
App/Program.cs
Remember to add the necessary usages.void RegisterCustomAppServices(IServiceCollection services, IConfiguration config) { services.AddTransient<IInstantiationProcessor, InstantiationProcessor>(); // Other custom services }
Moving custom code in RunInstantiationValidation
If RunInstantiationValidation
looks like the code below you have no custom code and can safely move on to Moving custom code in RunProcessTaskEnd. If not complete the steps below.
public override async Task<InstantiationValidationResult> RunInstantiationValidation(Instance instance)
{
return await _instantiationHandler.RunInstantiationValidation(instance);
}
- If you haven’t already: Create a new Class named
InstantiationValidator.cs
inApp/logic/Validation
and implement the interfaceAltinn.App.Core.Features.IInstantiationValidator
. The class can be named or placed where you like, this is only a suggestion. The file should look something like this now:using System.Threading.Tasks; using Altinn.App.Core.Features; using Altinn.App.Core.Models.Validation; using Altinn.Platform.Storage.Interface.Models; namespace Altinn.App.AppLogic.Validation; public class InstantiationValidator : IInstantiationValidator { public async Task<InstantiationValidationResult> Validate(Instance instance) { } }
- Move all the code from
RunInstantiationValidation
inÌnstantiationHandler.cs
into the methodValidate
in the class you just created. - Register
InstantiationValidator
in the methodRegisterCustomAppServices
App/Program.cs
Remember to add the necessary usages.void RegisterCustomAppServices(IServiceCollection services, IConfiguration config) { services.AddTransient<IInstantiationValidator, InstantiationValidator>(); // Other custom services }
Moving custom code in RunProcessTaskEnd
If RunProcessTaskEnd
looks like the code below you have no custom code and can safely move on to Moving custom code in GetPageOrder. If not complete the steps below.
public override async Task RunProcessTaskEnd(string taskId, Instance instance)
{
await Task.CompletedTask;
}
- Create a new class implementing the Interface
Altinn.App.Core.Features.IProcessTaskEnd
you can name and place this class wherever you like, but as a suggestion you can create and place it in the folderApp/logic/TaskProcessors
and name the file ProcessTaskEnd. The class should look something like this after the interface is implemented:using System.Threading.Tasks; using Altinn.App.Core.Features; using Altinn.Platform.Storage.Interface.Models; namespace Altinn.App.AppLogic.TaskProcessors; public class ProcessTaskEnd: IProcessTaskEnd { public Task End(string taskId, Instance instance) { return Task.CompletedTask; } }
- Move code from
RunProcessTaskEnd
to the methodIProcessTaskEnd
in the class implementing IProcessTaskEnd - Register your custom implementation of
IProcessTaskEnd
in the methodRegisterCustomAppServices
insideApp/Program.cs
Moving custom code in GetPageOrder
If GetPageOrder
in App/logic/App.cs
is not present you have no custom code and can safely move on the Removing App.cs. If not complete the steps below.
- Create a new class implementing the interface
Altinn.App.Core.Features.IPageOrder
you can name and place this class wherever you like in your project, but a suggestion is to name itPageOrder
and place it in the folderApp/logic/PageOrder
. The file should look something like this now:using System.Collections.Generic; using System.Threading.Tasks; using Altinn.App.Models; using Altinn.App.Core.Features; using Altinn.App.Core.Interface; using Altinn.App.Core.Models; namespace Altinn.App.AppLogic.PageOrder; public class PageOrder : IPageOrder { public async Task<List<string>> GetPageOrder(AppIdentifier appIdentifier, InstanceIdentifier instanceIdentifier, string layoutSetId, string currentPage, string dataTypeId, object formData) { } }
- Move the code from
GetPageOrder
to the methodGetPageOrder
in the class you just created. - Register your custom implementation of
IPageOrder
in the methodRegisterCustomAppServices
insideApp/Program.cs
Remember to add the necessary usages.void RegisterCustomAppServices(IServiceCollection services, IConfiguration config) { services.AddTransient<IPageOrder, PageOrder>(); // Other custom services }
Removing App.cs
Now all your custom code should be moved out of App.cs, take a look in the file and see if there is any code you need to move left.
See the old
default App.cs
here
If you don’t see any code worth keeping go ahead and delete the file. All of the logic Altinn used to have in this file is moved elsewhere in our code and should not leak into you application code.
Notable namespace changes
This is truly not a complete list of alle the changes to namespace we have done in v7, for that see the actual PR on Github
The following list is some of the namespaces that have changed that we think will affect most of the applications
- Interfaces that service owners naturally overrides/customize is moved from
Altinn.App.PlatformServices.Interfaces
toAltinn.App.Core.Features
Altinn.App.Common.Models
namespace is moved toAltinn.App.Core.Models
Altinn.App.PlatformServices.Interface.ICustomPdfHandler
interface is moved and renamed to:Altinn.App.Core.Features.Pdf.IPdfFormatter
Recommended plugin for Visual Studio Code
There are a lot of changes to namespaces so we strongly recommend using Visual Studio Code with the C# plugin installed. This will give you code help for importing and locating new or changed interfaces.
Plugin information:
Name: C#
Id: ms-dotnettools.csharp
Description: C# for Visual Studio Code (powered by OmniSharp).
Publisher: Microsoft
VS Marketplace Link: https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp