Scoped service in DBContext with DBContextFactory can't resolve from root provider - .net-core

I want to register a scoped ICurrentUserService and then use it in the DbContext to set the CreatedBy property on every entity. I want it scoped because it uses the current HttpContext to get the user id from the claims and the HttpContext is scoped by definition.
services.AddTransient<ICurrentUserService, CurrentUserService>();
However when I try to use this with the DbContextFactory like this:
services.AddDbContextFactory<MyDbContext>(options =>
options.UseSqlServer(config.ConnectionStrings.MSSQLConnection, x =>
{
x.MigrationsHistoryTable("...");
x.MigrationsAssembly("...");
}));
services.AddScoped<IMyDbContext>(provider => provider.GetRequiredService<IDbContextFactory<MyDbContext>>().CreateDbContext());
services.AddScoped<MyDbContext>(provider => provider.GetRequiredService<IDbContextFactory<MyDbContext>>().CreateDbContext());
I get the error
Cannot resolve scoped service 'SomeNamespace.ICurrentUserService' from root provider.
I can change it to Transient but that seems wrong and will probably be an issue later on for testing when I want to mock this and every class gets a new instance.
I'm actually not sure why Scoped does not work here. The DbContextFactory is registered Singleton but the Context is resolved as Scoped too.

Try it with ServiceLifetime.Scoped
services.AddDbContextFactory<ApplicationDbContext>(contextOptions =>
... YOUR OPTIONS
), ServiceLifetime.Scoped);

Related

Swagger API definition load API definition - Cant use schema id "$Module"

I am getting this exception when I want to create a Domain object with name Module
InvalidOperationException: Can't use schemaId "$Module" for type "$Pro.Core.Domain.Module". The same schemaId is already used for type "$System.Reflection.Module"
Swashbuckle.AspNetCore.SwaggerGen.SchemaRepository.RegisterType(Type type, string schemaId)
Seems like this name Module is conflicting with some System.Reflection.Module.
I have searched the internet and I have below 2 solutions that can get this working:
Rename my Module class name to something else but not Module(lets say MyModule)
Doing something like below
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(config =>
{
//some swagger configuration code.
//use fully qualified object names
config.CustomSchemaIds(x => x.FullName);
}
}
But I want to understand that what is there in name Module that creates this error. I don't see any reason for not being allowed to use this name as my domain class name.
Is there any way to use the Domain Object with name Module other that mentioned above in point 2 and why is this happening in the first place?
Fixed by using
services.AddSwaggerGen(options =>
{
options.CustomSchemaIds(type => type.ToString());
});
Find more details from
here

Ignore and DoNotValidate not working for AutoMapper 8.1 and .NET Core 2.1

I have developed an azure function in .net core and configured automapper in startup.cs by builder.Services.AddAutoMapper(Assembly.GetAssembly(this.GetType()));
I am trying to create a map between domain class (table in EF core) and a DTO. The domain class has a property RowVersion . I want this property to be ignored while mapping dto to domain.
For this I created a Profile Class and created my custom map but this is not working.
I tried DoNotValidate , but it doesn't seem to work
Startup.cs
builder.Services.AddAutoMapper(Assembly.GetAssembly(this.GetType()));
public class MapperProfile :Profile
{
public MapperProfile()
{
CreateMap<MyDto, MyDomain>().ForMember(i => i.RowVersion, opt => opt.Ignore());
}
}
Service.cs
_mapper.Map<myDomain>(myDtoInstance)
getting the below error
Unmapped members were found. Review the types and members below.
Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type
For no matching constructor, add a no-arg ctor, add optional arguments, or map all of the constructor parameters
Unmapped properties:
[5/28/2019 7:00:40 AM] RowVersion
ForMember is used for referencing the destination objects, in your case, MyDto.
Use ForSourceMember instead:
public MapperProfile()
{
CreateMap<MyDto, MyDomain>().ForSourceMember(i => i.RowVersion, opt => opt.Ignore());
}

How to release or distribute an application that uses mikro-orm?

In the configuration I have to specify the paths to .js and .ts files defining entities:
MikroORM.init({
...
entitiesDirs: ["build/entities"],
entitiesDirsTs: ["src/entities"],
});
So, when I will go to release or distribute the application. Will I need distribute the typescript code too? or will I need distribute only the cache generated? or will I need distribute both? or... none?
As of MikroORM v2.2
Now you can work with default metadata provider, it will require entity source files only if you do not provide entity or type options in your decorators (you can use entity callback to use reference to entity class instead of using string name in type, handle for refactoring via IDE like webstorm).
Original answer:
You should ship the typescript code too, and let the cache regenerate on the server - cache would be rebuilt anyway as it checks absolute path to cached entity for invalidation.
You could implement your own cache adapter or metadata provider to get around this, if you don't want to ship the typescript code.
This is how you could implement custom metadata provider that simply throws error when the type option is missing:
import { MetadataProvider, Utils } from 'mikro-orm';
import { EntityMetadata } from 'mikro-orm/dist/decorators';
export class SimpleMetadataProvider extends MetadataProvider {
async loadEntityMetadata(meta: EntityMetadata, name: string): Promise<void> {
// init types and column names
Object.values(meta.properties).forEach(prop => {
if (prop.entity) {
prop.type = Utils.className(prop.entity());
} else if (!prop.type) {
throw new Error(`type is missing for ${meta.name}.${prop.name}`)
}
});
}
}
Then provide this class when initializing:
const orm = await MikroORM.init({
// ...
metadataProvider: SimpleMetadataProvider,
});
The value of type should be JS types, like string/number/Date... You can observe your cached metadata to be sure what values should be there.
Also keep in mind that without TS metadata provider, you will need to specify entity type in #ManyToOne decorator too (either via entity callback, or as a string via type).

Is it possible to configure everything within context?

I am trying to configure Audit.net and define my custom logic for saving logs.
Is there a way to configure included entities within context?
I tried this
`
public ResidentMasterContext(DbContextOptions options) : base(options)
{
AuditDataProvider = new DynamicDataProvider();
Mode = AuditOptionMode.OptIn;
IncludeEntityObjects = true;
EntitySettings = new Dictionary<Type, EfEntitySettings>
{
{typeof(Apartment), new EfEntitySettings()}
};
}
`
but OnScopeSaving is not firing. And when I change mode to OptOut it takes all entities
I guess you are referring to the Audit.NET EntityFramework extension.
if you use OptIn you need to mark the included entities with [AuditInclude] attribute, or use the Include methods of the fluent API. You can check the documentation here.
An example using the fluent API for the EF configuration, to include only the entities User and UserDetail:
Audit.EntityFramework.Configuration.Setup()
.ForContext<ResidentMasterContext>(config => config
.IncludeEntityObjects())
.UseOptIn()
.Include<User>()
.Include<UserDetail>();
An example of the output configuration:
Audit.Core.Configuration.Setup()
.UseDynamicProvider(_ => _.OnInsertAndReplace(auditEvent =>
{
Console.WriteLine(auditEvent.ToJson());
}));

Getting a list of tagged services in my controller

What i want is to add services to the service container that i want to use later in my controller or service.
So i created two services with my custom tag fbeen.admin
here they are:
services:
app.test:
class: AppBundle\Admin\TestAdmin
tags:
- { name: fbeen.admin }
fbeen.admin.test:
class: Fbeen\AdminBundle\Admin\TestAdmin
tags:
- { name: fbeen.admin }
Now i want to use all the services with the tag fbeen.admin in my controller but i dont know how.
I followed the How to work with service tags tutorial but i get stuck on this rule:
$definition->addMethodCall('addTransport', array(new Reference($id)));
On some way the addTransport method of the TransportChain class should be called but it seems that it isn't been called.
And even if it would be called then i still do not have a list of services with the fbeen.admin tag into my controller.
I am sure that i am missing something but who can explain me what it is?
p.s. I know that compilerPass runs at buildtime but for example sonata admin knows all admin classes and twig knows all twig extensions. How do they know?
Thank you for reading this :-)
Symfony 3.3
Container gets compiled once (in debug more often, but in production only once). What you manage with addMethodCall... is that once you request your service from container, which you are storing in $definition (that in this case is controller). Then container will call method addMethodCall('method'.. during initialising your service.
What it will look in container:
// This is pseudo content of compiled container
$service = new MyController();
// This is what compiler pass addMethodCall will add, now its your
// responsibility to implement method addAdmin to store admins in for
// example class variable. This is as well way which sonata is using
$service->addAdmin(new AppBundle\Admin\TestAdmin());
$service->addAdmin(new AppBundle\Admin\TestAdmin());
return $service; // So you get fully initialized service
Symfony 3.4+
What you can do is this:
// Your services.yaml
services:
App/MyController/WantToInjectSerivcesController:
arguments:
$admins: !tagged fbeen.admin
// Your controller
class WantToInjectSerivcesController {
public function __construct(iterable $admins) {
foreach ($admins as $admin) {
// you hot your services here
}
}
}
Bonus autotagging of your services. Lets say all your controllers implements interface AdminInterface.
// In your extension where you building container or your kernel build method
$container->registerForAutoconfiguration(AdminInterface::class)->addTag('fbeen.admin');
This will tag automatically all services which implement your interface with tag. So you don't need to set tag explicitly.
The thing to note here is this: The CompilerPass doesn't run the 'addTransport' (or whatever you may call it) in the compiler-pass itself - just says 'when the time is right - run $definition->addTransport(...) class, with this data'. The place to look for where that happens is in your cache directory (grep -R TransportChain var/cache/), where it sets up the $transportChain->addTransport(...).
When you come to use that service for the first time - only then is the data filled in as the class is being instantiated from the container.
This worked for me:
extend the TransportChain class with a getTransports method:
public function getTransports()
{
return $this->transports;
}
and use the TransportChain service in my controller:
use AppBundle\Mail\TransportChain;
$transportChain = $this->get(TransportChain::class);
$transports = $transportChain->getTransports();
// $transports is now an array with all the tagged services
Thank you Alister Bulman for pushing me forwards :-)

Resources