[2.0] ChangeSets and Data Validation
Opened this issue · 2 comments
In CustomizedValidator
implementation, LocalValues
reflects the change requests and Resource
reflects the object's last state as expected, but the OriginalValues
doesn't get filled which makes it impossible to compare old and new data for validation purposes.
csproj
<PackageReference Include="Microsoft.Restier.AspNetCore" Version="1.0.0-ci-20211128-172920" />
<PackageReference Include="Microsoft.Restier.EntityFrameworkCore" Version="1.0.0-ci-20211128-172920" />
startup.cs
routeServices.AddChainedService<IChangeSetItemFilter, CustomizedSubmitProcessor>();
CustomizedValidator.cs
public class CustomizedValidator : IChangeSetItemValidator
{
private IChangeSetItemValidator Inner { get; set; }
public Task ValidateChangeSetItemAsync(SubmitContext context, ChangeSetItem item, Collection<ChangeSetItemValidationResult> validationResults, CancellationToken cancellationToken)
{
if (item is not DataModificationItem dataModificationItem)
{
return Inner.ValidateChangeSetItemAsync(context, item, validationResults, cancellationToken);
}
object entity = dataModificationItem.Resource;
bool isNewRequest = dataModificationItem.OriginalValues == null && dataModificationItem.ResourceKey == null;
bool isUpdateRequest = dataModificationItem.OriginalValues != null && dataModificationItem.LocalValues != null;
bool isDeleteRequest = dataModificationItem.LocalValues == null;
if (isUpdateRequest && entity is Organization organization)
{
dataModificationItem.LocalValues.TryGetValue("ParentId",out object localValueParentId);
dataModificationItem.OriginalValues.TryGetValue("ParentId", out object originalValueParentId);
bool changeExists = localValueParentId is not null && originalValueParentId is not null && localValueParentId != originalValueParentId;
if (organization.ParentId is not null && !changeExists)
{
var changeSetValidationResult = new ChangeSetItemValidationResult
{
Message = "You can not change this record's `ParentId`. It is not `null`.",
Severity = EventLevel.Error,
PropertyName = dataModificationItem.ResourceSetName + dataModificationItem.ResourceKey,
Target = entity
};
validationResults.Add(changeSetValidationResult);
}
using (var service = context.GetApiService<SmartProcureApi>())
{
var existingRecordParentId = service.DbContext.Organizations.Where(o => o.Id.Equals(organization.Id)).FirstOrDefault()?.ParentId;
if (existingRecordParentId != null && organization.ParentId!=existingRecordParentId)
{
}
}
}
return Inner.ValidateChangeSetItemAsync(context, item, validationResults, cancellationToken);
}
}
I'm not sure about why they designed it this way. The only way to make OriginalValues work in the pipeline is to load up the original object from the database, and then use the submitted data to then play the modifications and understand the differences.
This is the first implementation I've seen of anyone trying to use it the way it was designed. In my company's libraries, we do this sort of validation through the OnInserting/OnUpdating methods.
I would be interested in seeing if this architecture makes sense, because adding a database lookup on every call would slow the response rate down quite a bit.
We need to take a hard look at ChangeSets going into v2.
- They should track the changes made through the pipeline process.
- They should be able to load the values currently persisted in the DB (perhaps with a lock, if possible).
- They should work well with Batches.
- We should get rid of this stuff unless it's necessary.
If I come up with more "requirements" here, I'll add them.