Skip to content

Commit 8b90661

Browse files
committed
Update IDataClient and ModelSerializationService to support json in storage
When creating a new data element 1. Check appmetadata.DataTypes[type].AllowedContentTypes and see if `application/xml` or `application/json` appears first 2. Use fallback `application/xml` When getting or updating a data element 1. Look at dataElement.ContentType and use xml serialization unless it says `application/json` * [Obsolete] methods that does not work when storage format is dependent on DataElement.ContentType * Obsolete methods only work if DataType.AllowedContentTypes does not contain "application/json" * [Obsolete] methods in IDataClient that accepts org and app arguments and replace with similar methods without, as those are not required and is a hassle to provide.
1 parent 148d815 commit 8b90661

File tree

35 files changed

+660
-492
lines changed

35 files changed

+660
-492
lines changed

src/Altinn.App.Api/Controllers/DataController.cs

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -905,7 +905,7 @@ private async Task<ActionResult> GetBinaryData(
905905
DataElement dataElement
906906
)
907907
{
908-
Stream dataStream = await _dataClient.GetBinaryData(org, app, instanceOwnerPartyId, instanceGuid, dataGuid);
908+
Stream dataStream = await _dataClient.GetBinaryData(instanceOwnerPartyId, instanceGuid, dataGuid);
909909

910910
if (dataStream is not null)
911911
{
@@ -947,14 +947,7 @@ private async Task<ActionResult> GetFormData(
947947
)
948948
{
949949
// Get Form Data from data service. Assumes that the data element is form data.
950-
object appModel = await _dataClient.GetFormData(
951-
instanceGuid,
952-
_appModel.GetModelType(dataType.AppLogic.ClassRef),
953-
org,
954-
app,
955-
instanceOwnerId,
956-
dataGuid
957-
);
950+
object appModel = await _dataClient.GetFormData(instance, dataElement);
958951

959952
if (appModel is null)
960953
{
@@ -985,15 +978,7 @@ private async Task<ActionResult> GetFormData(
985978
{
986979
try
987980
{
988-
await _dataClient.UpdateData(
989-
appModel,
990-
instanceGuid,
991-
appModel.GetType(),
992-
org,
993-
app,
994-
instanceOwnerId,
995-
dataGuid
996-
);
981+
await _dataClient.UpdateFormData(instance, appModel, dataElement);
997982
}
998983
catch (PlatformHttpException e) when (e.Response.StatusCode is HttpStatusCode.Forbidden)
999984
{

src/Altinn.App.Api/Controllers/InstancesController.cs

Lines changed: 2 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
using Altinn.App.Core.Helpers;
1616
using Altinn.App.Core.Helpers.Serialization;
1717
using Altinn.App.Core.Internal.App;
18-
using Altinn.App.Core.Internal.AppModel;
1918
using Altinn.App.Core.Internal.Data;
2019
using Altinn.App.Core.Internal.Events;
2120
using Altinn.App.Core.Internal.Instances;
@@ -62,7 +61,6 @@ public class InstancesController : ControllerBase
6261
private readonly IProfileClient _profileClient;
6362

6463
private readonly IAppMetadata _appMetadata;
65-
private readonly IAppModel _appModel;
6664
private readonly AppImplementationFactory _appImplementationFactory;
6765
private readonly IPDP _pdp;
6866
private readonly IPrefill _prefillService;
@@ -87,7 +85,6 @@ public InstancesController(
8785
IInstanceClient instanceClient,
8886
IDataClient dataClient,
8987
IAppMetadata appMetadata,
90-
IAppModel appModel,
9188
IAuthenticationContext authenticationContext,
9289
IPDP pdp,
9390
IEventsClient eventsClient,
@@ -109,7 +106,6 @@ IServiceProvider serviceProvider
109106
_appMetadata = appMetadata;
110107
_altinnPartyClient = altinnPartyClient;
111108
_registerClient = serviceProvider.GetRequiredService<IRegisterClient>();
112-
_appModel = appModel;
113109
_appImplementationFactory = serviceProvider.GetRequiredService<AppImplementationFactory>();
114110
_pdp = pdp;
115111
_eventsClient = eventsClient;
@@ -960,8 +956,6 @@ private async Task CopyDataFromSourceInstance(
960956
Instance sourceInstance
961957
)
962958
{
963-
string org = application.Org;
964-
string app = application.AppIdentifier.App;
965959
int instanceOwnerPartyId = int.Parse(targetInstance.InstanceOwner.PartyId, CultureInfo.InvariantCulture);
966960

967961
string[] sourceSplit = sourceInstance.Id.Split("/");
@@ -987,28 +981,7 @@ Instance sourceInstance
987981
{
988982
DataType dt = dts.First(dt => dt.Id.Equals(de.DataType, StringComparison.Ordinal));
989983

990-
Type type;
991-
try
992-
{
993-
type = _appModel.GetModelType(dt.AppLogic.ClassRef);
994-
}
995-
catch (Exception altinnAppException)
996-
{
997-
throw new ServiceException(
998-
HttpStatusCode.InternalServerError,
999-
$"App.GetAppModelType failed: {altinnAppException.Message}",
1000-
altinnAppException
1001-
);
1002-
}
1003-
1004-
object data = await _dataClient.GetFormData(
1005-
sourceInstanceGuid,
1006-
type,
1007-
org,
1008-
app,
1009-
instanceOwnerPartyId,
1010-
Guid.Parse(de.Id)
1011-
);
984+
object data = await _dataClient.GetFormData(sourceInstance, de);
1012985

1013986
if (application.CopyInstanceSettings.ExcludedDataFields != null)
1014987
{
@@ -1026,15 +999,7 @@ await _prefillService.PrefillDataModel(
1026999

10271000
ObjectUtils.InitializeAltinnRowId(data);
10281001

1029-
await _dataClient.InsertFormData(
1030-
data,
1031-
Guid.Parse(targetInstance.Id.Split("/")[1]),
1032-
type,
1033-
org,
1034-
app,
1035-
instanceOwnerPartyId,
1036-
dt.Id
1037-
);
1002+
await _dataClient.InsertFormData(targetInstance, dt.Id, data);
10381003

10391004
await UpdatePresentationTextsOnInstance(application.PresentationFields, targetInstance, dt.Id, data);
10401005
await UpdateDataValuesOnInstance(application.DataFields, targetInstance, dt.Id, data);
@@ -1067,8 +1032,6 @@ await _dataClient.InsertFormData(
10671032
if (binaryDataTypes.Any(dt => dt.Id.Equals(de.DataType, StringComparison.Ordinal)))
10681033
{
10691034
using var binaryDataStream = await _dataClient.GetBinaryData(
1070-
org,
1071-
app,
10721035
instanceOwnerPartyId,
10731036
sourceInstanceGuid,
10741037
Guid.Parse(de.Id)

src/Altinn.App.Core/EFormidling/Implementation/DefaultEFormidlingService.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,6 @@ ValidAltinnEFormidlingConfiguration config
250250
usedFileNames.Add(uniqueFileName);
251251

252252
await using Stream stream = await _dataClient.GetBinaryData(
253-
applicationMetadata.Org,
254-
applicationMetadata.AppIdentifier.App,
255253
instanceOwnerPartyId,
256254
instanceGuid,
257255
new Guid(dataElement.Id)

src/Altinn.App.Core/Features/Action/UniqueSignatureAuthorizer.cs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,7 @@ public async Task<bool> AuthorizeAction(UserActionAuthorizerContext context)
7575
var signatureDataElements = instance.Data.Where(d => dataTypes.Contains(d.DataType)).ToList();
7676
foreach (var signatureDataElement in signatureDataElements)
7777
{
78-
var signee = await GetSigneeFromSignDocument(
79-
appMetadata.AppIdentifier,
80-
context.InstanceIdentifier,
81-
signatureDataElement
82-
);
78+
var signee = await GetSigneeFromSignDocument(context.InstanceIdentifier, signatureDataElement);
8379
bool unauthorized = context.Authentication switch
8480
{
8581
Authenticated.User a => a.UserId.ToString(CultureInfo.InvariantCulture) == signee?.UserId,
@@ -97,14 +93,11 @@ public async Task<bool> AuthorizeAction(UserActionAuthorizerContext context)
9793
}
9894

9995
private async Task<Signee?> GetSigneeFromSignDocument(
100-
AppIdentifier appIdentifier,
10196
InstanceIdentifier instanceIdentifier,
10297
DataElement dataElement
10398
)
10499
{
105100
await using var data = await _dataClient.GetBinaryData(
106-
appIdentifier.Org,
107-
appIdentifier.App,
108101
instanceIdentifier.InstanceOwnerPartyId,
109102
instanceIdentifier.InstanceGuid,
110103
Guid.Parse(dataElement.Id)

src/Altinn.App.Core/Features/Signing/Services/SigningReceiptService.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,6 @@ IDataClient dataClient
206206
.WithSendersReference(element.Id)
207207
.WithData(
208208
await dataClient.GetDataBytes(
209-
appMetadata.AppIdentifier.Org,
210-
appMetadata.AppIdentifier.App,
211209
instanceIdentifier.InstanceOwnerPartyId,
212210
instanceIdentifier.InstanceGuid,
213211
Guid.Parse(element.Id)

src/Altinn.App.Core/Features/Telemetry/Telemetry.DataClient.cs

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,19 @@ partial class Telemetry
1313
return activity;
1414
}
1515

16-
internal Activity? StartInsertFormDataActivity(Guid? instanceId, int? partyId)
16+
internal Activity? StartUpdateDataActivity(Guid instanceId, Guid dataElementId)
1717
{
18-
var activity = ActivitySource.StartActivity($"{Prefix}.InsertFormData");
18+
var activity = ActivitySource.StartActivity($"{Prefix}.UpdateData");
1919
activity?.SetInstanceId(instanceId);
20-
activity?.SetInstanceOwnerPartyId(partyId);
20+
activity?.SetDataElementId(dataElementId);
2121
return activity;
2222
}
2323

24-
internal Activity? StartUpdateDataActivity(Guid instanceId, Guid dataElementId)
24+
internal Activity? StartUpdateDataActivity(Instance instance, DataElement dataElement)
2525
{
2626
var activity = ActivitySource.StartActivity($"{Prefix}.UpdateData");
27-
activity?.SetInstanceId(instanceId);
28-
activity?.SetDataElementId(dataElementId);
27+
activity?.SetInstanceId(instance);
28+
activity?.SetDataElementId(dataElement);
2929
return activity;
3030
}
3131

@@ -45,14 +45,6 @@ partial class Telemetry
4545
return activity;
4646
}
4747

48-
internal Activity? StartDeleteBinaryDataActivity(Guid? instanceId, int? partyId)
49-
{
50-
var activity = ActivitySource.StartActivity($"{Prefix}.DeleteBinaryData");
51-
activity?.SetInstanceId(instanceId);
52-
activity?.SetInstanceOwnerPartyId(partyId);
53-
return activity;
54-
}
55-
5648
internal Activity? StartInsertBinaryDataActivity(Guid? instanceId, int? partyId)
5749
{
5850
var activity = ActivitySource.StartActivity($"{Prefix}.InsertBinaryData");
@@ -106,6 +98,13 @@ partial class Telemetry
10698
return activity;
10799
}
108100

101+
internal Activity? StartGetFormDataActivity(Instance? instance)
102+
{
103+
var activity = ActivitySource.StartActivity($"{Prefix}.GetFormData");
104+
activity?.SetInstanceId(instance);
105+
return activity;
106+
}
107+
109108
internal Activity? StartLockDataElementActivity(string? instanceId, Guid? dataGuid)
110109
{
111110
var activity = ActivitySource.StartActivity($"{Prefix}.LockDataElement");

src/Altinn.App.Core/Helpers/Serialization/ModelSerializationService.cs

Lines changed: 89 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Xml;
55
using System.Xml.Serialization;
66
using Altinn.App.Core.Features;
7+
using Altinn.App.Core.Infrastructure.Clients.Storage;
78
using Altinn.App.Core.Internal.AppModel;
89
using Altinn.App.Core.Models.Result;
910
using Altinn.Platform.Storage.Interface.Models;
@@ -38,22 +39,75 @@ public ModelSerializationService(IAppModel appModel, Telemetry? telemetry = null
3839
/// <param name="data">The binary data</param>
3940
/// <param name="dataType">The data type used to get content type and the classRef for the object to be returned</param>
4041
/// <returns>The model specified in </returns>
42+
[Obsolete("DeserializeFromStorage needs a DataElement parameter to support json in storage")]
4143
public object DeserializeFromStorage(ReadOnlySpan<byte> data, DataType dataType)
4244
{
45+
if (DataClient.TypeAllowsJson(dataType))
46+
{
47+
throw new InvalidOperationException(
48+
$"Data type {dataType.Id} allows application/json and must use DeserializeFromStorage with DataElement specified"
49+
);
50+
}
4351
var type = GetModelTypeForDataType(dataType);
44-
45-
// TODO: support sending json to storage based on dataType.ContentTypes
4652
return DeserializeXml(data, type);
4753
}
4854

55+
/// <summary>
56+
/// Deserialize binary data from storage to a model of the classRef specified in the dataType
57+
/// </summary>
58+
/// <param name="data">The binary data</param>
59+
/// <param name="dataType">The data type used to get content type and the classRef for the object to be returned</param>
60+
/// <param name="dataElement"></param>
61+
/// <returns>The model specified in </returns>
62+
public object DeserializeFromStorage(ReadOnlySpan<byte> data, DataType dataType, DataElement dataElement)
63+
{
64+
var type = GetModelTypeForDataType(dataType);
65+
66+
return dataElement.ContentType?.ToLowerInvariant() switch
67+
{
68+
"application/xml" => DeserializeXml(data, type),
69+
"application/json" => DeserializeJson(data, type),
70+
null or "" => throw new InvalidOperationException(
71+
$"Data element {dataElement.Id} does not have a content type"
72+
), // Consider defaulting to xml after initial testing?
73+
_ => throw new InvalidOperationException(
74+
$"Unsupported content type {dataElement.ContentType} on data element {dataElement.Id}"
75+
),
76+
};
77+
}
78+
4979
/// <summary>
5080
/// Serialize an object to binary data for storage, respecting classRef and content type in dataType
5181
/// </summary>
5282
/// <param name="model">The object to serialize (must match the classRef in DataType)</param>
5383
/// <param name="dataType">The data type</param>
5484
/// <returns>the binary data and the content type (currently only application/xml, but likely also json in the future)</returns>
5585
/// <exception cref="InvalidOperationException">If the classRef in dataType does not match type of the model</exception>
86+
[Obsolete("SerializeToStorage needs a DataElement parameter to support json in storage")]
5687
public (ReadOnlyMemory<byte> data, string contentType) SerializeToStorage(object model, DataType dataType)
88+
{
89+
if (DataClient.TypeAllowsJson(dataType))
90+
{
91+
throw new InvalidOperationException(
92+
$"Data type {dataType.Id} allows application/json and must use SerializeToStorage with DataElement specified"
93+
);
94+
}
95+
return SerializeToStorage(model, dataType, null);
96+
}
97+
98+
/// <summary>
99+
/// Serialize an object to binary data for storage, respecting classRef and content type in dataType
100+
/// </summary>
101+
/// <param name="model">The object to serialize (must match the classRef in DataType)</param>
102+
/// <param name="dataType">The data type</param>
103+
/// <param name="dataElement">The existing data element to preserve content type</param>
104+
/// <returns>the binary data and the content type (application/xml or application/json)</returns>
105+
/// <exception cref="InvalidOperationException">If the classRef in dataType does not match type of the model</exception>
106+
public (ReadOnlyMemory<byte> data, string contentType) SerializeToStorage(
107+
object model,
108+
DataType dataType,
109+
DataElement? dataElement
110+
)
57111
{
58112
var type = GetModelTypeForDataType(dataType);
59113
if (type != model.GetType())
@@ -63,8 +117,15 @@ public object DeserializeFromStorage(ReadOnlySpan<byte> data, DataType dataType)
63117
);
64118
}
65119

66-
//TODO: support sending json to storage based on dataType.ContentTypes
67-
return (SerializeToXml(model), "application/xml");
120+
var contentType = dataElement is not null ? dataElement.ContentType : FindFirstContentType(dataType);
121+
122+
return contentType?.ToLowerInvariant() switch
123+
{
124+
"application/json" => (SerializeToJson(model), "application/json"),
125+
// When the content type is missing, default to xml for backward compatibility
126+
"application/xml" or "" or null => (SerializeToXml(model), "application/xml"),
127+
_ => throw new InvalidOperationException($"Unsupported content type {contentType}"),
128+
};
68129
}
69130

70131
/// <summary>
@@ -125,7 +186,7 @@ DataType dataType
125186

126187
var modelType = GetModelTypeForDataType(dataType);
127188
object model;
128-
if (contentType?.Contains("application/xml") ?? true) // default to xml if no content type is provided
189+
if (contentType?.Contains("application/xml", StringComparison.Ordinal) ?? true) // default to xml if no content type is provided
129190
{
130191
try
131192
{
@@ -145,7 +206,7 @@ DataType dataType
145206
};
146207
}
147208
}
148-
else if (contentType.Contains("application/json"))
209+
else if (contentType.Contains("application/json", StringComparison.Ordinal))
149210
{
150211
try
151212
{
@@ -315,4 +376,26 @@ public object GetEmpty(DataType dataType)
315376
ArgumentException.ThrowIfNullOrWhiteSpace(dataType?.AppLogic?.ClassRef);
316377
return _appModel.Create(dataType.AppLogic.ClassRef);
317378
}
379+
380+
private static string FindFirstContentType(DataType dataType)
381+
{
382+
if (dataType.AllowedContentTypes is not null)
383+
{
384+
foreach (var contentType in dataType.AllowedContentTypes)
385+
{
386+
if ("application/xml".Equals(contentType, StringComparison.OrdinalIgnoreCase))
387+
{
388+
return "application/xml";
389+
}
390+
if ("application/json".Equals(contentType, StringComparison.OrdinalIgnoreCase))
391+
{
392+
return "application/json";
393+
}
394+
}
395+
}
396+
397+
// If no supported content type is found, default to xml for backward compatibility
398+
// Valid apps will never trigger this
399+
return "application/xml";
400+
}
318401
}

src/Altinn.App.Core/Implementation/DefaultAppEvents.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,6 @@ private async Task AutoDeleteDataElements(Instance instance)
6868
{
6969
deleteTasks.Add(
7070
_dataClient.DeleteData(
71-
applicationMetadata.Org,
72-
applicationMetadata.AppIdentifier.App,
7371
int.Parse(instance.InstanceOwner.PartyId, CultureInfo.InvariantCulture),
7472
Guid.Parse(item.InstanceGuid),
7573
Guid.Parse(item.Id),

0 commit comments

Comments
 (0)