Is it possible to pass StartupService as a parameter in CreateHostBuilder? - c#

In order to create the project generic, I would like to receive the StartupService as a parameter. Is it possible?
public static IHostBuilder CreateHostBuilder<T, TClass>(string[] args, T service, TClass clas, Type type) => Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder =>
{
var SERVICE_PORT = EnvironmentVariable.Get("SERVICE_PORT", 8200);
webBuilder.UseUrls($"https://+:{SERVICE_PORT}");
webBuilder.UseStartup<StartupService>();
//webBuilder.Ser
})
.ConfigureServices(svc => { svc.Add(new ServiceDescriptor(type, service));
});
I would like something similar to:
public static IHostBuilder CreateHostBuilder<T>(string[] args, T service, Type type, **TClass StartupServiceFromExternalClass**)

Yes, you can use the overload of UseStartup that accepts a startupFactory:
public static IHostBuilder CreateHostBuilder<T, TStartup>(
string[] args,
T service,
Type type,
TStartup startupServiceFromExternalClass)
where TStartup : class
{
return Host
.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
var SERVICE_PORT = EnvironmentVariable.Get("SERVICE_PORT", 8200);
webBuilder.UseUrls($"https://+:{SERVICE_PORT}");
webBuilder.UseStartup(() => startupServiceFromExternalClass);
})
.ConfigureServices(svc => svc.Add(new ServiceDescriptor(type, service)));
}

Related

Automapper issue where properties in derived types are not assigned

I think i'm doing something stupid or this is a bug.
Consider the following code:
using System;
using System.Collections.Generic;
using AutoMapper;
public class OrderViewModel {
public Guid Id {get;set;}
}
public class OrderSummaryViewModel : OrderViewModel {
public int NumberOfLines {get;set;}
}
public class MyGenericResponseObject<T> {
public List<T> Items {get;set;} = new();
}
public class MyResponseObject : MyGenericResponseObject<OrderSummaryViewModel> {
}
public class Program
{
public static void Main()
{
var e1 = new MyGenericResponseObject<OrderSummaryViewModel>
{
Items = new() {
new() { Id = Guid.NewGuid(), NumberOfLines = 420 }
}
};
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<OrderSummaryViewModel, OrderViewModel>();
cfg.CreateMap<OrderViewModel, OrderSummaryViewModel>();
cfg.CreateMap<MyGenericResponseObject<OrderSummaryViewModel>, MyResponseObject>();
});
var mapper = config.CreateMapper();
var e2 = mapper.Map<MyGenericResponseObject<OrderSummaryViewModel>, MyResponseObject>(e1);
foreach (var i in e1.Items)
{
Console.WriteLine($"{i.Id} - {i.NumberOfLines}");
}
Console.WriteLine("------------------");
foreach (var i in e2.Items)
{
Console.WriteLine($"{i.Id} - {i.NumberOfLines}");
}
}
}
https://dotnetfiddle.net/tcoRvK
Now since the type of e1 is MyGenericResponseObject<OrderSummaryViewModel> i'm expecting the two lists to be equal after the mapping. Instead it outputs the following:
701cde6c-4751-4419-a2cb-1d347a3eb0e6 - 420
------------------
701cde6c-4751-4419-a2cb-1d347a3eb0e6 - 0
For some reason after the mapping the NumberOfLines property is its default...
How come? is there something wrong with my configuration perhaps - or is it a limitation of AutoMapper or is it a bug?
Any help would be welcome.
using net6 and latest stable automapper (see the fiddle).
EDIT:
Fildor suggested playing with the mapping config:
var config = new MapperConfiguration(cfg =>
{
//cfg.CreateMap<OrderSummaryViewModel, OrderViewModel>();
cfg.CreateMap<OrderViewModel, OrderSummaryViewModel>();
cfg.CreateMap<MyGenericResponseObject<OrderSummaryViewModel>, MyResponseObject>();
});
Returns:
1d31bf14-0bf2-4a10-8c4d-92efa9d6e131 - 420
------------------
1d31bf14-0bf2-4a10-8c4d-92efa9d6e131 - 0
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<OrderSummaryViewModel, OrderViewModel>();
//cfg.CreateMap<OrderViewModel, OrderSummaryViewModel>();
cfg.CreateMap<MyGenericResponseObject<OrderSummaryViewModel>, MyResponseObject>();
});
Returns:
Unhandled exception. System.ArgumentException: Expression of type 'OrderViewModel' cannot be used for parameter of type 'OrderSummaryViewModel' of method 'Void Add(OrderSummaryViewModel)' (Parameter 'arg0')
at System.Dynamic.Utils.ExpressionUtils.ValidateOneArgument(MethodBase method, ExpressionType nodeKind, Expression arguments, ParameterInfo pi, String methodParamName, String argumentParamName, Int32 index)
at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, Expression arg0)
at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, IEnumerable`1 arguments)
at AutoMapper.Internal.Mappers.CollectionMapper.<MapExpression>g__MapCollectionCore|2_1(Expression destExpression, <>c__DisplayClass2_0& )
at AutoMapper.Internal.Mappers.CollectionMapper.MapExpression(IGlobalConfiguration configuration, ProfileMap profileMap, MemberMap memberMap, Expression sourceExpression, Expression destExpression)
at AutoMapper.Execution.ExpressionBuilder.MapExpression(IGlobalConfiguration configuration, ProfileMap profileMap, TypePair typePair, Expression source, MemberMap memberMap, Expression destination)
at AutoMapper.Execution.TypeMapPlanBuilder.MapMember(MemberMap memberMap, Expression destinationMemberValue, ParameterExpression resolvedValue)
at AutoMapper.Execution.TypeMapPlanBuilder.CreatePropertyMapFunc(MemberMap memberMap, Expression destination, MemberInfo destinationMember)
at AutoMapper.Execution.TypeMapPlanBuilder.CreateAssignmentFunc(Expression createDestination)
at AutoMapper.Execution.TypeMapPlanBuilder.CreateMapperLambda()
at AutoMapper.TypeMap.CreateMapperLambda(IGlobalConfiguration configuration)
at AutoMapper.TypeMap.Seal(IGlobalConfiguration configuration)
at AutoMapper.MapperConfiguration.<.ctor>g__Seal|20_0()
at AutoMapper.MapperConfiguration..ctor(MapperConfigurationExpression configurationExpression)
at AutoMapper.MapperConfiguration..ctor(Action`1 configure)
at Program.Main()
Command terminated by signal 6
var config = new MapperConfiguration(cfg =>
{
//cfg.CreateMap<OrderSummaryViewModel, OrderViewModel>();
//cfg.CreateMap<OrderViewModel, OrderSummaryViewModel>();
cfg.CreateMap<MyGenericResponseObject<OrderSummaryViewModel>, MyResponseObject>();
});
Returns:
d9e72e9e-9bc0-41fb-bf4d-33242dca5027 - 420
------------------
d9e72e9e-9bc0-41fb-bf4d-33242dca5027 - 420
I believe that is happening because OrderViewModel doesn't contain NumberOfLines property, so that's why your implicit mapping fails (or value is set to default)
cfg.CreateMap<OrderSummaryViewModel, OrderViewModel>()
If you described that explicitly, you would get compiler error:
cfg.CreateMap<OrderSummaryViewModel, OrderViewModel>()
.ForPath(
dest => dest.Id,
opts => opts.MapFrom(src => src.Id))
.ForPath(
dest => dest.NumberOfLines,
opts => opts.MapFrom(src => src.NumberOfLines));
And when you do mapping from other side (OrderViewModel -> OrderSummaryViewModel), OrderSummaryViewModel property NumberOfLines is set to a default int value.
So by leaving only following line, everything is correctly mapped as those object properties are present on both ends, so they can be auto-mapped.
cfg.CreateMap<MyGenericResponseObject<OrderSummaryViewModel>, MyResponseObject>();

Setup add function in mock of a DbSet Entity framawork to change vale of a object property?

if you use an add function, then the property id of the element to include will be changed for a value assign from the table, How can i to do this?.
First. create a generic class that define the operations:
public static Mock<DbSet<T>> SetupMockDbSet<T>(IList<T> dataToBeReturnedOnGet) where T : class
{
var mocks = dataToBeReturnedOnGet.AsQueryable();
var mockSet = new Mock<DbSet<T>>();
mockSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(new TestAsyncQueryProvider<T>(mocks.Provider));
mockSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(mocks.Expression);
mockSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(mocks.ElementType);
mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(mocks.GetEnumerator());
mockSet.Setup(d => d.Add(It.IsAny<T>())).Callback<T>((s) => ModifiedProcess(s));
mockSet.As<IAsyncEnumerable<T>>()
.Setup(x => x.GetAsyncEnumerator(It.IsAny<CancellationToken>()))
.Returns(new TestAsyncEnumerator<T>(mocks.GetEnumerator()));
return mockSet;
}
The function add use Callback, who can access to parameters sended, and after define a function who has a process that depend of the object sended
public static void ModifiedProcess<T>(T parameter) where T : class
{
if (parameter is ObjectDTO)
{
ObjectDTO obj2 = (ObjectDTO)Convert.ChangeType(parameter, typeof(ObjectDTO));
obj2.Id = 5;
}
}

Mapping profile instance doesn't creates

I'm trying to add automapping models with reflection, I created an interface IMapFrom<> and implemented it in all dtos.
Then I created class
public class MappingProfile : Profile
{
public MappingProfile(Assembly assembly)
=> ApplyMappingsFromAssembly(assembly);
private void ApplyMappingsFromAssembly(Assembly assembly)
{
var types = assembly
.GetExportedTypes()
.Where(t => t
.GetInterfaces()
.Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom<>)))
.ToList();
foreach (var type in types)
{
var instance = Activator.CreateInstance(type);
const string mappingMethodName = "Mapping";
var methodInfo = type.GetMethod(mappingMethodName)
?? type.GetInterface("IMapFrom`1")?.GetMethod(mappingMethodName);
methodInfo?.Invoke(instance, new object[] { this });
}
}
}
And add it in service collection
public static IServiceCollection AddAutoMapperProfile(IServiceCollection services, Assembly assembly)
=> services
.AddAutoMapper(
(_, config) => config
.AddProfile(new MappingProfile(Assembly.GetCallingAssembly())),
Array.Empty<Assembly>());
Why the class instance is not created? Because of this I can't convert the model into a dto
Try like this:
Define IMapFrom:
public interface IMapFrom<T>
{
void Mapping(Profile profile) => profile.CreateMap(typeof(T), GetType());
}
Then add Mapping profile:
public class MappingProfile : Profile
{
public MappingProfile()
{
ApplyMappingsFromAssembly(Assembly.GetExecutingAssembly());
}
private void ApplyMappingsFromAssembly(Assembly assembly)
{
var types = assembly.GetExportedTypes()
.Where(t => t.GetInterfaces().Any(i =>
i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom<>)))
.ToList();
foreach (var type in types)
{
var instance = Activator.CreateInstance(type);
var methodInfo = type.GetMethod("Mapping")
?? type.GetInterface("IMapFrom`1")?.GetMethod("Mapping");
methodInfo?.Invoke(instance, new object[] { this });
}
}
}
Create DI method:
public static IServiceCollection AddAutoMapperProfile(this IServiceCollection services)
{
var assembly = Assembly.GetExecutingAssembly();
services.AddAutoMapper(assembly);
return services;
}
and at the end call DI method that you created:
...
services.AddAutoMapperProfile();
...

Params keyword with tuple with delegate in it. C#

I'm trying to create a function with the params keyword like this:
public static IHostBuilder Foo(this IHostBuilder hostBuilder, params (string key, Action<IConsulConfigurationSource> options)[] sources)
{
return hostBuilder.
}
But on the method call:
Host.Foo(("key", bar =>
{
bar. // `bar` can not determine type of IConsulConfigurationSource
}));
The Action<IConsulConfigurationSource> variable bar can not determine type of IConsulConfigurationSource.
When I've modified method like this:
public static IHostBuilder Foo(this IHostBuilder hostBuilder, params Action<IConsulConfigurationSource>[] options sources)
{
return hostBuilder.
}
It works perfectly:
Host.Foo(bar =>
{
bar.ReloadOnChange = true;
});
Any idea why this is happening?

Get instance of a class that inherits from abstract class and have specific value in a property

I'm working on a factory to create concrete instances based in two criteria:
1) The class must inherit from an specific abstract class
2) The class must have an specific value in an overridden property
My code looks like this:
public abstract class CommandBase
{
public abstract string Prefix { get; }
}
public class PaintCommand : CommandBase
{
public override string Prefix { get; } = "P";
}
public class WalkCommand : CommandBase
{
public override string Prefix { get; } = "W";
}
class Program
{
static void Main(string[] args)
{
var paintCommand = GetInstance("P");
var walkCommand = GetInstance("W");
Console.ReadKey();
}
static CommandBase GetInstance(string prefix)
{
try
{
var currentAssembly = Assembly.GetExecutingAssembly();
var concreteType = currentAssembly.GetTypes().Where(t => t.IsSubclassOf(typeof(CommandBase)) &&
!t.IsAbstract &&
t.GetProperty("Prefix").GetValue(t).ToString() == prefix).SingleOrDefault();
if (concreteType == null)
throw new InvalidCastException($"No concrete type found for command: {prefix}");
return (CommandBase)Activator.CreateInstance(concreteType);
}
catch (Exception ex)
{
return default(CommandBase);
}
}
}
I'm getting the error:
{System.Reflection.TargetException: Object does not match target type. at System.Reflection.RuntimeMethodInfo.CheckConsistency(Object target) at System.Reflection.RuntimeMethodInfo.InvokeArgumentsCheck(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
A cleaner way is to define your own attribute to store the prefix.
[AttributeUsage(AttributeTargets.Class)]
public class CommandAttribute : Attribute
{
public String Prefix { get; set; }
public CommandAttribute(string commandPrefix)
{
Prefix = commandPrefix;
}
}
Then use them like so:
[CommandAttribute("P")]
public class PaintCommand : CommandBase
{}
[CommandAttribute("W")]
public class WalkCommand : CommandBase
{}
In reflection:
static CommandBase GetInstance(string prefix)
{
var currentAssembly = Assembly.GetExecutingAssembly();
var concreteType = currentAssembly.GetTypes().Where(commandClass => commandClass.IsDefined(typeof(CommandAttribute), false) && commandClass.GetCustomAttribute<CommandAttribute>().Prefix == prefix).FirstOrDefault();
if (concreteType == null)
throw new InvalidCastException($"No concrete type found for command: {prefix}");
return (CommandBase)Activator.CreateInstance(concreteType);
}
As spender alluded to in his comment, the reason you are getting this specific error is this line:
t.GetProperty("Prefix").GetValue(t)
Here, t is a Type variable containing a class, such as WalkCommand. You're getting the PropertyInfo object for the Prefix property on that class, then trying to use GetValue() to read the value of that property from an instance of a WalkCommand object.
The problem is that you aren't passing GetValue() an instance of the WalkCommand class, you're passing it a Type so Reflection is then throwing this exception.
There are a few of ways to deal with this:
1) Create an instance of each type on the fly, just to read its prefix (I really wouldn't recommend doing this):
var instance = currentAssembly.GetTypes()
.Where(t => t.IsSubclassOf(typeof(CommandBase)) && !t.IsAbstract)
.Select(t => new { t, i = (CommandBase)Activator.CreateInstance(t) })
.Where(x => x.t.GetProperty("Prefix").GetValue(x.i).ToString() == prefix)
.Select(x => x.i)
.SingleOrDefault();
return instance;
2) Change the whole thing over to use Attributes, such as in SwiftingDuster's answer
3) Use a static constructor to create a Dictionary that maps the string prefices to the concrete types. Reflection is expensive, and the classes aren't going to change (unless you're dynamically loading assemblies), so do it once.
We could do this by (ab)using my earlier "create an instance to throw it away" code, so we're still creating an instance of each class just to read the property, but at least we only do it once:
static Dictionary<string, Type> prefixMapping;
static Program()
{
prefixMapping = currentAssembly.GetTypes()
.Where(t => t.IsSubclassOf(typeof(CommandBase)) && !t.IsAbstract)
.Select(t => new { t, c = (CommandBase)Activator.CreateInstance(t) })
.ToDictionary(x => x.t.GetProperty("Prefix").GetValue(x.c).ToString(), x => x.t);
}
static CommandBase GetInstance(string prefix)
{
Type concreteType;
if ( prefixMapping.TryGetValue(prefix, out concreteType) )
{
return (CommandBase)Activator.CreateInstance(concreteType);
}
return default(CommandBase);
}
Note that this will throw a really horrible exception if you have multiple classes with the same prefix, and as it would be an exception in a static constructor, it'll likely explode your application. Either catch the exception (which will leave prefixMapping as null), or modify the Linq expression to only return one type for each prefix (as below).
4) Use both the Attribute method from SwiftingDuster and also the precomputation of the dictionary. This would be my preferred solution:
static Dictionary<string, Type> prefixMapping;
static Program()
{
prefixMapping = currentAssembly.GetTypes()
.Where(t => t.IsSubclassOf(typeof(CommandBase)) && t.IsDefined(typeof(CommandAttribute), false) && !t.IsAbstract)
.Select(t => new { t, p = t.GetCustomAttribute<CommandAttribute>().Prefix })
.GroupBy(x => x.p)
.ToDictionary(g => g.Key, g => g.First().t);
}
static CommandBase GetInstance(string prefix)
{
Type concreteType;
if ( prefixMapping.TryGetValue(prefix, out concreteType) )
{
return (CommandBase)Activator.CreateInstance(concreteType);
}
return default(CommandBase);
}
Hope this helps

Categories

Resources