Skip to content

Commit

Permalink
Added support for invoke
Browse files Browse the repository at this point in the history
  • Loading branch information
ackava committed Jan 28, 2025
1 parent 5b9dec1 commit 7e0418c
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 54 deletions.
37 changes: 37 additions & 0 deletions NeuroSpeech.EntityAccessControl/BaseEntityController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
using Microsoft.AspNetCore.Mvc.Filters;
using System.Linq.Expressions;
using NeuroSpeech.EntityAccessControl.Extensions;
using Microsoft.EntityFrameworkCore.ChangeTracking;

namespace NeuroSpeech.EntityAccessControl
{
Expand Down Expand Up @@ -230,6 +231,42 @@ public async Task<IActionResult> Save(
}
}

[HttpPost("invoke/{entityName}/{methodName}")]
public async Task<IActionResult> Invoke(
[FromRoute] string entityName,
[FromRoute] string methodName,
[FromBody] JsonElement body,
CancellationToken cancellationToken
)
{
var et = FindEntityType(entityName);
var iet = db.GetEntityEvents(et.ClrType);

var entity = body.GetProperty("entity");
var args = body.GetProperty("args");

var e1 = await db.FindByKeysAsync(et, body, cancellationToken);

if (e1 == null)
{
throw new EntityAccessException("Entity not found");
}

var r = await DbEntityMethodInvoker.CallMethod(db, methodName, e1, args);

if (r == null)
{
return Json(null);
}

if (r is IActionResult ar)
{
return ar;
}

return Serialize(r);
}


[HttpPut("multiple")]
[HttpPost("multiple")]
Expand Down
112 changes: 60 additions & 52 deletions NeuroSpeech.EntityAccessControl/CompositeKeyAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,52 +1,60 @@
#nullable enable
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace NeuroSpeech.EntityAccessControl
{

//[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
//public class DbFunctionAttribute: Attribute
//{

//}

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class ExternalFunctionAttribute: Attribute
{

}


[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
public class CompositeKeyAttribute: Attribute
{
public readonly string[] Names;

public CompositeKeyAttribute(params string[] names)
{
this.Names = names;
}

}

public static class CompositeKeyAttributeExtensions
{
public static void RegisterCompositeKeys(this ModelBuilder modelBuilder)
{
foreach(var entityType in modelBuilder.Model.GetEntityTypes())
{
var ck = entityType.ClrType.GetCustomAttribute<CompositeKeyAttribute>();
if (ck == null)
continue;
modelBuilder.Entity(entityType.ClrType, (a) => {
a.HasKey(ck.Names);
});
}
}
}
}
#nullable enable
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace NeuroSpeech.EntityAccessControl
{

//[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
//public class DbFunctionAttribute: Attribute
//{

//}

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class ExternalFunctionAttribute: Attribute
{

}



[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class ExternalMethodAttribute : Attribute
{

}


[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
public class CompositeKeyAttribute: Attribute
{
public readonly string[] Names;

public CompositeKeyAttribute(params string[] names)
{
this.Names = names;
}

}

public static class CompositeKeyAttributeExtensions
{
public static void RegisterCompositeKeys(this ModelBuilder modelBuilder)
{
foreach(var entityType in modelBuilder.Model.GetEntityTypes())
{
var ck = entityType.ClrType.GetCustomAttribute<CompositeKeyAttribute>();
if (ck == null)
continue;
modelBuilder.Entity(entityType.ClrType, (a) => {
a.HasKey(ck.Names);
});
}
}
}
}
74 changes: 72 additions & 2 deletions NeuroSpeech.EntityAccessControl/Extensions/DbFunctionInvoker.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using NeuroSpeech.EntityAccessControl.Internal;
using System;
using System.Collections.Generic;
Expand All @@ -11,8 +14,75 @@

namespace NeuroSpeech.EntityAccessControl.Extensions
{

internal class DbEntityMethodInvoker
{
public static Task<object?> CallMethod(ISecureQueryProvider db, string function, object entity, JsonElement args)
{
var length = args.GetArrayLength();

var a = new QueryParameter[length];

for (var i = 0; i < length; i++)
{
a[i] = new QueryParameter(args[i]);
}
return Generic.InvokeAs(db.GetType(), entity.GetType(), CallTypedFunction<DbContext, object>, db, function, entity, a);
}

public static async Task<object?> CallTypedFunction<TDB, T>(ISecureQueryProvider db, string method, object entity, QueryParameter[] a)
where TDB : DbContext
where T : class
{
var type = typeof(T);

var key = ("static-method", type.Name, method);

var f = type.StaticCacheGetOrCreate(key, () => CompileFunction<TDB,T>(db, method, typeof(TDB), type));

var r = f((TDB)db ,(T)entity , a);
if (r is Task task)
{
return await task.GetResultAsObject();
}

return null;
}

static Func<TDB, T, QueryParameter[], object> CompileFunction<TDB, T>(ISecureQueryProvider db, string method, Type context, Type entityType)
{
var et = db.GetEntityEvents(entityType);
var m = et?.GetType().GetMethod(method);
if (m == null || m.GetCustomAttribute<ExternalFunctionAttribute>() == null) {
return (x, y, z) => throw new EntityAccessException("Method " + method + " is not extenral");
}

var pContext = Expression.Parameter(context);
var pEntity = Expression.Parameter(entityType);
var pArgs = Expression.Parameter(typeof(QueryParameter[]));

var pList = new List<Expression>() { pEntity };

var paremters = m.GetParameters();

var length = paremters.Length;

for (var i = 0; i < length; i++)
{
var p = paremters[i];
pList.Add(Expression.Convert( Expression.ArrayIndex(pArgs, Expression.Constant(i)), p.ParameterType));
}

var body = Expression.Call(pContext, m, pList.ToArray());

var r = Expression.Lambda<Func<TDB, T, QueryParameter[], object>>(body, pContext, pEntity, pArgs);
return r.Compile();
}
}

internal class DbFunctionInvoker
{
{

public static async Task<IQueryable<T>> CallFunction<T>(ISecureQueryProvider db, string function, JsonElement parameters)
where T : class
{
Expand Down
13 changes: 13 additions & 0 deletions NeuroSpeech.EntityAccessControl/Parser/QueryParameter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@
namespace NeuroSpeech.EntityAccessControl
{

public class QueryParameters
{
private readonly JsonElement args;

public QueryParameters(JsonElement args)
{
this.args = args;
}



}

public readonly struct QueryParameter: IEnumerable<object>
{
private readonly JsonElement element;
Expand Down
27 changes: 27 additions & 0 deletions NeuroSpeech.EntityAccessControl/TypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,17 @@ public static T InvokeAs<T1, T2, T3, T>(Type type, Type type2, Func<T1, T2, T3,
return method(p1, p2, p3);
}

public static T InvokeAs<T1, T2, T3, T4, T>(Type type, Type type2, Func<T1, T2, T3, T4, T> fx, T1 p1, T2 p2, T3 p3, T4 p4)
{
var method = TypedGet(
(type, type2, fx.Method),
(k) => k.method
.GetGenericMethodDefinition()
.MakeGenericMethod(k.type1, k.type2)
.CreateTypedDelegate<Func<T1, T2, T3, T4, T>>());
return method(p1, p2, p3, p4);
}

public static T InvokeAs<Target, T>(this Target target, Type type, Func<T> fx)
{
var method = TypedGet(
Expand Down Expand Up @@ -708,5 +719,21 @@ public static class TaskExtensions
var r = await task;
return r;
}

public static async Task<object?> GetResultAsObject(this Task task)
{
var t = task.GetType();
if (t.IsConstructedGenericType)
{
return await (Task<object?>)Generic.InvokeAs(t.GetFirstGenericArgument(), GetResultFrom<object>, task);
}
await task;
return null;
}

private static async Task<object?> GetResultFrom<T>(Task task)
{
return await (task as Task<T>)!;
}
}
}

0 comments on commit 7e0418c

Please sign in to comment.