Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Resolve async Gateway requests
  • Loading branch information
mythz committed Mar 18, 2020
1 parent 5c228b8 commit 6dae6bc
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 88 deletions.
169 changes: 83 additions & 86 deletions src/ServiceStack.Server/AutoQueryFeature.AutoCrud.cs
Expand Up @@ -23,29 +23,28 @@ public async Task<object> Create<Table>(ICreateDb<Table> dto, IRequest req)
{
//TODO: Allow Create to use Default Values
using var db = GetDb<Table>(req);
using (Profiler.Current.Step("AutoQuery.Create"))
{
var response = await ExecAndReturnResponseAsync<Table>(dto, db,async ctx => {
var dtoValues = ResolveDtoValues(req, dto);
var pkFieldDef = typeof(Table).GetModelMetadata()?.PrimaryKey;
var isAutoId = pkFieldDef?.AutoId == true;
var selectIdentity = ctx.IdProp != null || ctx.ResultProp != null;
var autoIntId = await db.InsertAsync<Table>(dtoValues, selectIdentity: selectIdentity);
var providedId = pkFieldDef != null && dtoValues.ContainsKey(pkFieldDef.Name);
using var profiler = Profiler.Current.Step("AutoQuery.Create");

var response = await ExecAndReturnResponseAsync<Table>(dto, db,async ctx => {
var dtoValues = ResolveDtoValues(req, dto);
var pkFieldDef = typeof(Table).GetModelMetadata()?.PrimaryKey;
var isAutoId = pkFieldDef?.AutoId == true;
var selectIdentity = ctx.IdProp != null || ctx.ResultProp != null;
var autoIntId = await db.InsertAsync<Table>(dtoValues, selectIdentity: selectIdentity);
var providedId = pkFieldDef != null && dtoValues.ContainsKey(pkFieldDef.Name);
// [AutoId] Guid's populate the PK Property or return Id if provided
if (isAutoId || providedId)
return new ExecValue(pkFieldDef.GetValue(dtoValues), selectIdentity ? 1 : autoIntId);
return selectIdentity
? new ExecValue(autoIntId, 1)
: pkFieldDef != null && dtoValues.TryGetValue(pkFieldDef.Name, out var idValue)
? new ExecValue(idValue, autoIntId)
: new ExecValue(null, autoIntId);
});
// [AutoId] Guid's populate the PK Property or return Id if provided
if (isAutoId || providedId)
return new ExecValue(pkFieldDef.GetValue(dtoValues), selectIdentity ? 1 : autoIntId);
return selectIdentity
? new ExecValue(autoIntId, 1)
: pkFieldDef != null && dtoValues.TryGetValue(pkFieldDef.Name, out var idValue)
? new ExecValue(idValue, autoIntId)
: new ExecValue(null, autoIntId);
});

return response;
}
return response;
}

public Task<object> Update<Table>(IUpdateDb<Table> dto, IRequest req)
Expand All @@ -62,78 +61,76 @@ public Task<object> Patch<Table>(IPatchDb<Table> dto, IRequest req)
public async Task<object> Delete<Table>(IDeleteDb<Table> dto, IRequest req)
{
using var db = GetDb<Table>(req);
using (Profiler.Current.Step("AutoQuery.Delete"))
{
var response = await ExecAndReturnResponseAsync<Table>(dto, db,
async ctx => {
var dtoValues = ResolveDtoValues(req, dto, skipDefaults:true);
//Should have at least 1 non-default filter
if (dtoValues.Count == 0)
throw new NotSupportedException($"'{dto.GetType().Name}' did not contain any filters");
var meta = AutoCrudMetadata.Create(dto.GetType());
var pkFieldDef = meta.ModelDef.PrimaryKey;
var idValue = pkFieldDef != null && dtoValues.TryGetValue(pkFieldDef.Name, out var oId)
? oId
: null;
// Should only update a Single Row
if (GetAutoFilterExpressions(db, dto, dtoValues, req, out var expr, out var exprParams))
using var profiler = Profiler.Current.Step("AutoQuery.Delete");

var response = await ExecAndReturnResponseAsync<Table>(dto, db,
async ctx => {
var dtoValues = ResolveDtoValues(req, dto, skipDefaults:true);
//Should have at least 1 non-default filter
if (dtoValues.Count == 0)
throw new NotSupportedException($"'{dto.GetType().Name}' did not contain any filters");
var meta = AutoCrudMetadata.Create(dto.GetType());
var pkFieldDef = meta.ModelDef.PrimaryKey;
var idValue = pkFieldDef != null && dtoValues.TryGetValue(pkFieldDef.Name, out var oId)
? oId
: null;
// Should only update a Single Row
if (GetAutoFilterExpressions(db, dto, dtoValues, req, out var expr, out var exprParams))
{
//If there were Auto Filters, construct filter expression manually by adding any remaining DTO values
foreach (var entry in dtoValues)
{
//If there were Auto Filters, construct filter expression manually by adding any remaining DTO values
foreach (var entry in dtoValues)
{
var fieldDef = meta.ModelDef.GetFieldDefinition(entry.Key);
if (fieldDef == null)
throw new NotSupportedException($"Unknown '{entry.Key}' Field in '{dto.GetType().Name}' IDeleteDb<{typeof(Table).Name}> Request");
if (expr.Length > 0)
expr += " AND ";
var fieldDef = meta.ModelDef.GetFieldDefinition(entry.Key);
if (fieldDef == null)
throw new NotSupportedException($"Unknown '{entry.Key}' Field in '{dto.GetType().Name}' IDeleteDb<{typeof(Table).Name}> Request");
if (expr.Length > 0)
expr += " AND ";
var quotedColumn = db.GetDialectProvider().GetQuotedColumnName(meta.ModelDef, fieldDef);
var quotedColumn = db.GetDialectProvider().GetQuotedColumnName(meta.ModelDef, fieldDef);
expr += quotedColumn + " = {" + exprParams.Count + "}";
exprParams.Add(entry.Value);
}
var q = db.From<Table>();
q.Where(expr, exprParams.ToArray());
return new ExecValue(idValue, await db.DeleteAsync(q));
}
else
{
return new ExecValue(idValue, await db.DeleteAsync<Table>(dtoValues));
expr += quotedColumn + " = {" + exprParams.Count + "}";
exprParams.Add(entry.Value);
}
});

return response;
}
var q = db.From<Table>();
q.Where(expr, exprParams.ToArray());
return new ExecValue(idValue, await db.DeleteAsync(q));
}
else
{
return new ExecValue(idValue, await db.DeleteAsync<Table>(dtoValues));
}
});

return response;
}

public async Task<object> Save<Table>(ISaveDb<Table> dto, IRequest req)
{
using var db = GetDb<Table>(req);
using (Profiler.Current.Step("AutoQuery.Save"))
{
var row = dto.ConvertTo<Table>();
var response = await ExecAndReturnResponseAsync<Table>(dto, db,
//TODO: Use Upsert when available
fn: async ctx => {
await db.SaveAsync(row);
object idValue = null;
var pkField = typeof(Table).GetModelMetadata().PrimaryKey;
if (pkField != null)
{
var propGetter = TypeProperties.Get(dto.GetType()).GetPublicGetter(pkField.Name);
if (propGetter != null)
idValue = propGetter(dto);
}
return new ExecValue(idValue, 1);
});
using var profiler = Profiler.Current.Step("AutoQuery.Save");

var row = dto.ConvertTo<Table>();
var response = await ExecAndReturnResponseAsync<Table>(dto, db,
//TODO: Use Upsert when available
fn: async ctx => {
await db.SaveAsync(row);
object idValue = null;
var pkField = typeof(Table).GetModelMetadata().PrimaryKey;
if (pkField != null)
{
var propGetter = TypeProperties.Get(dto.GetType()).GetPublicGetter(pkField.Name);
if (propGetter != null)
idValue = propGetter(dto);
}
return new ExecValue(idValue, 1);
});

return response;
}
return response;
}

internal struct ExecValue
Expand Down Expand Up @@ -515,7 +512,7 @@ public abstract partial class AutoQueryServiceBase
/// <summary>
/// Inserts new entry into Table
/// </summary>
public virtual async Task<object> Create<Table>(ICreateDb<Table> dto) => AutoQuery.Create(dto, Request);
public virtual Task<object> Create<Table>(ICreateDb<Table> dto) => AutoQuery.Create(dto, Request);

/// <summary>
/// Updates entry into Table
Expand All @@ -530,11 +527,11 @@ public abstract partial class AutoQueryServiceBase
/// <summary>
/// Deletes entry from Table
/// </summary>
public virtual async Task<object> Delete<Table>(IDeleteDb<Table> dto) => AutoQuery.Delete(dto, Request);
public virtual Task<object> Delete<Table>(IDeleteDb<Table> dto) => AutoQuery.Delete(dto, Request);

/// <summary>
/// Inserts or Updates entry into Table
/// </summary>
public virtual async Task<object> Save<Table>(ISaveDb<Table> dto) => AutoQuery.Save(dto, Request);
public virtual Task<object> Save<Table>(ISaveDb<Table> dto) => AutoQuery.Save(dto, Request);
}
}
1 change: 1 addition & 0 deletions src/ServiceStack/Validators.cs
Expand Up @@ -119,6 +119,7 @@ public static class Validators
public static Dictionary<string, string> ConditionErrorCodes { get; } = new Dictionary<string, string>();
public static Dictionary<string, string> ErrorCodeMessages { get; } = new Dictionary<string, string>();

//TODO FV9: ValidatorOptions.Global.CascadeMode;
static readonly Func<CascadeMode> CascadeMode = () => ValidatorOptions.CascadeMode;

public static bool HasValidateRequestAttributes(Type type) => type.HasAttribute<ValidateRequestAttribute>();
Expand Down
28 changes: 28 additions & 0 deletions tests/ServiceStack.WebHost.Endpoints.Tests/AutoQueryCrudModels.cs
Expand Up @@ -150,6 +150,15 @@ public abstract class UpdateAuditBase<Table,TResponse> : IUpdateDb<Table>, IRetu
[AutoFilter(QueryTerm.Ensure, nameof(IAuditTenant.TenantId), Eval = "Request.Items.TenantId")]
public abstract class UpdateAuditTenantBase<Table,TResponse> : UpdateAuditBase<Table,TResponse> {}

[Authenticate]
[AutoPopulate(nameof(IAudit.ModifiedDate), Eval = "utcNow")]
[AutoPopulate(nameof(IAudit.ModifiedBy), Eval = "userAuthName")] //or userAuthId
[AutoPopulate(nameof(IAudit.ModifiedInfo), Eval = "`${userSession.DisplayName} (${userSession.City})`")]
public abstract class PatchAuditBase<Table,TResponse> : IPatchDb<Table>, IReturn<TResponse> {}

[AutoFilter(QueryTerm.Ensure, nameof(IAuditTenant.TenantId), Eval = "Request.Items.TenantId")]
public abstract class PatchAuditTenantBase<Table,TResponse> : PatchAuditBase<Table,TResponse> {}

[Authenticate]
[AutoPopulate(nameof(IAudit.SoftDeletedDate), Eval = "utcNow")]
[AutoPopulate(nameof(IAudit.SoftDeletedBy), Eval = "userAuthName")] //or userAuthId
Expand Down Expand Up @@ -181,6 +190,13 @@ public class UpdateRockstarAuditTenant : UpdateAuditTenantBase<RockstarAuditTena
public LivingStatus? LivingStatus { get; set; }
}

public class PatchRockstarAuditTenant : PatchAuditTenantBase<RockstarAuditTenant, RockstarWithIdAndResultResponse>
{
public int Id { get; set; }
public string FirstName { get; set; }
public LivingStatus? LivingStatus { get; set; }
}

public class CreateRockstarAuditTenantGateway : IReturn<RockstarWithIdAndResultResponse>
{
public string FirstName { get; set; }
Expand All @@ -198,6 +214,18 @@ public class UpdateRockstarAuditTenantGateway : IReturn<RockstarWithIdAndResultR
public LivingStatus? LivingStatus { get; set; }
}

public class PatchRockstarAuditTenantGateway : IReturn<RockstarWithIdAndResultResponse>
{
public int Id { get; set; }
public string FirstName { get; set; }
public LivingStatus? LivingStatus { get; set; }
}

public class RealDeleteAuditTenantGateway : IReturn<RockstarWithIdResponse>
{
public int Id { get; set; }
}

public class SoftDeleteAuditTenant : SoftDeleteAuditTenantBase<RockstarAuditTenant, RockstarWithIdAndResultResponse>
{
public int Id { get; set; }
Expand Down
42 changes: 40 additions & 2 deletions tests/ServiceStack.WebHost.Endpoints.Tests/AutoQueryCrudTests.cs
Expand Up @@ -12,16 +12,34 @@ namespace ServiceStack.WebHost.Endpoints.Tests
{
public class AutoCrudGatewayServices : Service
{
public object Any(CreateRockstarAuditTenantGateway request)
public async Task<object> Any(CreateRockstarAuditTenantGateway request)
{
var gatewayRequest = request.ConvertTo<CreateRockstarAuditTenant>();
var response = Gateway.Send(gatewayRequest);
var sync = Gateway.Send(gatewayRequest);
var response = await Gateway.SendAsync(gatewayRequest);
return response;
}

public async Task<object> Any(UpdateRockstarAuditTenantGateway request)
{
var gatewayRequest = request.ConvertTo<UpdateRockstarAuditTenant>();
var sync = Gateway.Send(gatewayRequest);
var response = await Gateway.SendAsync(gatewayRequest);
return response;
}

public async Task<object> Any(PatchRockstarAuditTenantGateway request)
{
var gatewayRequest = request.ConvertTo<PatchRockstarAuditTenant>();
var sync = Gateway.Send(gatewayRequest);
var response = await Gateway.SendAsync(gatewayRequest);
return response;
}

public async Task<object> Any(RealDeleteAuditTenantGateway request)
{
var gatewayRequest = request.ConvertTo<RealDeleteAuditTenant>();
var sync = Gateway.Send(gatewayRequest);
var response = await Gateway.SendAsync(gatewayRequest);
return response;
}
Expand Down Expand Up @@ -697,6 +715,26 @@ public void Can_CreateRockstarAuditTenantGateway_Gateway()
Assert.That(result.Age, Is.EqualTo(createRequest.Age));
Assert.That(result.DateOfBirth.Date, Is.EqualTo(createRequest.DateOfBirth.Date));
Assert.That(result.LivingStatus, Is.EqualTo(updateRequest.LivingStatus));

var patchRequest = new PatchRockstarAuditTenantGateway {
Id = createResponse.Id,
FirstName = "PatchedGateway",
LivingStatus = LivingStatus.Alive,
};
var patchResponse = authClient.Patch(patchRequest);
result = patchResponse.Result;

Assert.That(updateResponse.Id, Is.EqualTo(createResponse.Id));
Assert.That(result.FirstName, Is.EqualTo(patchRequest.FirstName));
Assert.That(result.LastName, Is.EqualTo(createRequest.LastName));
Assert.That(result.Age, Is.EqualTo(createRequest.Age));
Assert.That(result.DateOfBirth.Date, Is.EqualTo(createRequest.DateOfBirth.Date));
Assert.That(result.LivingStatus, Is.EqualTo(patchRequest.LivingStatus));

var deleteRequest = authClient.Delete(new RealDeleteAuditTenantGateway {
Id = createResponse.Id,
});
Assert.That(deleteRequest.Id, Is.EqualTo(createResponse.Id));
}

[Test]
Expand Down

0 comments on commit 6dae6bc

Please sign in to comment.