You’ll still get an SQL Exception if you have RDBMS Foreign Key constraints that are violated which will enforce the constraint at the DB Level.
Like normal ServiceStack Services you can also use Global Request Filters to add your own logic, e.g:
GlobalRequestFiltersAsync.Add(async (req,res,dto) => {
if (dto is CreateTable createTable)
{
using var db = HostContext.AppHost.GetDbConnection(req);
if (await db.ExistsAsync<RefTable>(x => x.RefId == createTable.Id))
{
res.StatusCode = 400;
res.StatusDescription = "HasForeignKeyReferences";
res.EndRequest();
}
}
});
For other options you’ll need to download the latest v5.8.1 to access most of the new features mentioned below…
[ValidateRequest] Examples
You can include specifying [ValidateRequest]
condition validation rule where you can use the #Script DbScripts and its API Reference to execute
[ValidateRequest(Condition =
"!dbExistsSync('SELECT * FROM RefTable WHERE RefId = @Id', { dto.Id })",
ErrorCode = "HasForeignKeyReferences")]
public class CreateTable : ICreateDb<Table>, IReturn<IdResponse>
{
public int Id { get; set; }
}
Type Validators
The latest release has also added support for “Type Validators” which is new in ServiceStack (i.e. not in Fluent Validation) where [ValidateRequest]
now matches the functionality of [Validate]
property attribute where the constructor argument lets you specify a #Script that returns an ITypeValidator
(i.e. instead of a FV IPropertyValidator
).
So you can now specify a #Script reference to a custom Type Validator like:
[ValidateRequest("NoRefTableReferences", ErrorCode = "HasForeignKeyReferences")]
public class CreateTable : ICreateDb<Table>, IReturn<IdResponse>, IHasId<int>
{
public int Id { get; set; }
}
And define the validation logic in your Custom Validator, e.g:
class NoRefTableReferences : TypeValidator
{
public NoRefTableReferences()
: base("HasForeignKeyReferences", "Has RefTable References") {}
public override async Task<bool> IsValidAsync(object dto, IRequest request)
{
//Example of dynamic access using compiled accessor delegates
//(int)TypeProperties.Get(dto.GetType()).GetPublicGetter("Id")(dto);
var id = ((IHasId<int>)dto).Id;
using var db = HostContext.AppHost.GetDbConnection(request);
return !(await db.ExistsAsync<RefTable>(x => x.RefId == id));
}
}
Then register it to your AppHost’s ScriptContext as normal, e.g:
public class MyValidators : ScriptMethods
{
public ITypeValidator NoRefTableReferences() => new NoRefTableReferences();
}
Which you can register in your AppHost by adding like any other Script Method:
ScriptContext.ScriptMethods.AddRange(new ScriptMethods[] {
new DbScriptsAsync(),
new MyValidators(),
});
If you’re using #Script Pages SharpPagesFeature
you’ll need to register the plugin before adding the above script methods to ScriptContext
or you can add them when registering the plugin, e.g:
Plugins.Add(new SharpPagesFeature {
ScriptMethods = {
new DbScriptsAsync(),
new MyValidators(),
},
});
Multiple Type Validators
You can combine multiple validators by either returning an array of validators, e.g:
[ValidateRequest("[NoTable1References,NoTable2References]")]
Or having multiple attributes:
[ValidateRequest("NoTable1References")]
[ValidateRequest("NoTable2References")]
Type Validators
I’ll take this time to provide more info about Type Validators. Once nice characteristic about them is that they’re decoupled from their implementation which unlike [Authenticate]
and [RequiredRole]
etc don’t require any implementation .dll’s so they’re safe to use in your ServiceModel project so they’re recommended for implementation-less Services like AutoQuery/Crud.
Another nice characteristic (e.g. over #Script “Conditions”) is that they’re only evaluated & resolved once on Startup so if you accidentally delete a Validator but you still have a Request DTO referencing it, it will throw a Exception on Startup.
Built-in Type Validators
The ValidateScripts.cs source shows all of ServiceStack’s built-in Property and Type Validators:
public class ValidateScripts : ScriptMethods
{
//...
public ITypeValidator IsAuthenticated() => new IsAuthenticatedValidator();
public ITypeValidator IsAuthenticated(string provider) => new IsAuthenticatedValidator(provider);
public ITypeValidator HasRole(string role) => new HasRolesValidator(role);
public ITypeValidator HasRoles(string[] roles) => new HasRolesValidator(roles);
public ITypeValidator HasPermission(string permission) => new HasPermissionsValidator(permission);
public ITypeValidator HasPermissions(string[] permission) => new HasPermissionsValidator(permission);
public ITypeValidator IsAdmin() => new HasRolesValidator(RoleNames.Admin);
}
Which are also published in #Script API Reference and you can find the source code for of all their implementations in TypeValidators.cs
So you can enforce Authentication with:
[ValidateRequest("IsAuthenticated")]
Or require Roles/Permissions:
[ValidateRequest("[HasRole('Manager'),HasPermission('ViewAccounts')]")]
Or limit to only admin users with:
[ValidateRequest("IsAdmin")]
DB Validation Source
Just like Property Validators you can assign them at runtime from a dynamic source like a DB where you can add ValidateRule
without a Field
to assign Type Validators to specific requests, e.g:
var validationSource = container.Resolve<IValidationSource>();
validationSource.InitSchema();
validationSource.SaveValidationRules(new List<ValidateRule> {
new ValidateRule { Type=nameof(CreateTable), Validator = "NoRefTableReferences" },
new ValidateRule { Type=nameof(MyRequest), Field=nameof(MyRequest.LastName), Validator = "NotNull" },
new ValidateRule { Type=nameof(MyRequest), Field=nameof(MyRequest.Age), Validator = "InclusiveBetween(13,100)" },
});