-
Notifications
You must be signed in to change notification settings - Fork 133
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Splitting ISieveCustomFilterMethods into multiple files #104
Comments
Maybe partial classes? The dependency injection is tied to the interface. So you can not define and declare to inject multiple concrete classes for the I don't like partial classes, so I come around to that behaviour with this:
Kind of an OVERKILL approach. But.. I think it's very powerful and very flexible when declaring custom methods or registering entities to able used with spf, for some I may not want to use by default. Therefore I can use something like this:
|
Hi @hasanmanzak, Very useful information. Do you have a sample of this that you could share? Thanks. |
@hasanmanzak your approach may be valuable to some but for simplicity sake it would be nice to be able to do as @wobbince suggested and it seems to me it should be simple to achieve. .Net DI allows for adding multiple instances of the same interface (using IServiceCollection.AddScoped instead of IServiceCollection.TryAddScoped) which can be injected via |
@hasanmanzak Is there any new update about supporting this in any coming release? |
We used Vertical Slicing in our Architecture and therefore put the ISieveCustomFilterMethods in the same place as our endpoint. Is there an update on this? |
I also had the need to split custom sieve filters into different files (even different projects). I solved the problem by creating a composite sieve filter by creating IL code to copy methods from existing filters into a new filter. public class CompositeSieveCustomFilterBuilder
{
private static Type? type = null;
public static Type Build(params Type[] types)
{
if (type != null) return type;
AssemblyName assemblyName = new AssemblyName("DynamicAssembly");
AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("DynamicModule");
TypeBuilder typeBuilder = moduleBuilder.DefineType("DynamicCompositeSieveCustomFilter", TypeAttributes.Public);
typeBuilder.AddInterfaceImplementation(typeof(ISieveCustomFilterMethods));
foreach (Type t in types)
{
if (t.GetInterface(nameof(ISieveCustomFilterMethods)) == null)
throw new Exception($"{t.Name} does not implement ISieveCustomFilterMethods");
foreach (MethodInfo method in t.GetMethods(BindingFlags.Instance | BindingFlags.Public))
{
//method signature should be
//IQueryable<T> Name(IQueryable<T> source, string op, string[] values)
var parameters = method.GetParameters();
if (parameters.Length == 3
&& parameters[0].ParameterType == method.ReturnType
&& parameters[1].ParameterType == typeof(string)
&& parameters[2].ParameterType == typeof(string[]))
{
MethodBuilder methodBuilder = typeBuilder.DefineMethod(method.Name, method.Attributes, method.ReturnType, method.GetParameters().Select(p => p.ParameterType).ToArray());
ILGenerator il = methodBuilder.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Ldarg_3);
il.Emit(OpCodes.Callvirt, method);
il.Emit(OpCodes.Ret);
}
}
}
type = typeBuilder.CreateType();
if (type == null)
{
throw new Exception("DynamicCompositeSieveCustomFilter cannot be created");
}
return type;
}
} Then, in the Program.cs, dependency is registrered as builder.Services.AddScoped<ISieveCustomFilterMethods>(factory =>
{
Type compositeSieve = CompositeSieveCustomFilterBuilder.Build(typeof(FirstCustomFilters), typeof(SecondCustomFilters));
object? instance = Activator.CreateInstance(compositeSieve);
if (instance == null)
throw new Exception("Activator.CreateInstance(compositeSieve) returned null!");
return (ISieveCustomFilterMethods) instance;
}); |
@boris612 I like it! Shouldn't really be necessary but it is a viable workaround. |
Really a good solution for making it workable. I've modified the below code to pass the types dynamically
|
This is really a good solution. But if the custom filter class has a constructor where we need to inject any service then how we can modify your code to achieve this? public class CustomFilter : ISieveCustomFilterMethods |
This cannot be done, because that constructor is never invoked. If you add a breakpoint in your filter method and try to see what is There is a dirty workaround to achieve what you want, by generating IL code for the constructor of typeBuilder.AddInterfaceImplementation(typeof(ISieveCustomFilterMethods)); //we had this line already, and the next block is a new one
FieldBuilder fieldBuilder = typeBuilder.DefineField("serviceProvider", typeof(IServiceProvider), FieldAttributes.Private);
var constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, new[] { typeof(IServiceProvider) });
ILGenerator ctorIL = constructorBuilder.GetILGenerator();
LocalBuilder localBuilder = ctorIL.DeclareLocal(typeof(IServiceProvider));
ctorIL.Emit(OpCodes.Ldarg_1);
ctorIL.Emit(OpCodes.Stloc, localBuilder);
ctorIL.Emit(OpCodes.Ldarg_0);
ctorIL.Emit(OpCodes.Ldloc, localBuilder);
ctorIL.Emit(OpCodes.Stfld, fieldBuilder);
ctorIL.Emit(OpCodes.Ret);
... //existing code Then the previous code builder.Services.AddScoped<ISieveCustomFilterMethods>(factory =>
{
Type compositeSieve = CompositeSieveCustomFilterBuilder.Build(typeof(FirstCustomFilters), typeof(SecondCustomFilters));
object? instance = Activator.CreateInstance(compositeSieve);
if (instance == null)
throw new Exception("Activator.CreateInstance(compositeSieve) returned null!");
return (ISieveCustomFilterMethods) instance;
}); has to be changed into builder.Services.AddScoped<ISieveCustomFilterMethods>(factory =>
{
Type compositeSieve = CompositeSieveCustomFilterBuilder.Build(typeof(FirstCustomFilters), typeof(SecondCustomFilters));
object? instance = Activator.CreateInstance(compositeSieve, factory); //we have add an IServiceProvider instance
if (instance == null)
throw new Exception("Activator.CreateInstance(compositeSieve) returned null!");
return (ISieveCustomFilterMethods) instance;
}); Then you can change your code to something like this public class CustomFilter : ISieveCustomFilterMethods {
private IServiceProvider serviceProvider;
public IQueryable filter(IQueryable, string op, string[] values) {
ISomeService someService = this.serviceProvider.GetService<ISomeService>();
...
}
} |
@boris612 Thanks for your reply. This is a great solution and I am able to solve my problem. |
A little late to this, but if you're using this Sieve within an endpoint, you can always create a factory to resolve the filter methods. As simple as: services.AddScoped<ISieveCustomFilterMethods>(provider =>
{
var currentContext = provider.GetRequiredService<IHttpContextAccessor>().HttpContext;
var currentPath = currentContext.Request.Path.ToString();
if(currentPath.Contains("/user/search")) {
return new UserSieveFilterService();
}
....
return new DefaultSieveFilterService();
} |
Hi,
Is it possible to split the ISieveCustomFilterMethods implementation into multiple small, manageable files?
I.e. one per entity?
Thanks.
The text was updated successfully, but these errors were encountered: