Aspnetboilerplate ObjectMapper or Logger is null - c#

I have a small assembly that utilises the following NuGet packages Abp, Abp.AspNetCore and Abp.AbpAutoMapper.
For reference I am using Abp 6.4.0
I have added the depends to the module, the main service is also below.
The constructor inject is valid for both IObjectMapper and Ilogger. Whereas the inherited DomainService and ApplicationService for ObjectMapper and Logger are always null / NullLogger.Instance or NullObjectMapper.Instance.
[DependsOn(typeof(AbpAutoMapperModule))]
public class ServiceModule:AbpModule {
// other methods and logic
public override void PostInitialize(){
var mainService = Configuration.Get<IMainService>();
Task.Run(() =>
{
mainService.Start();
});
base.PostInitialize();
}
}
public class MainService : ApplicationService, IMainService
{
public MainService(IObjectMapper mapper, ILogger logger)
{
if (Logger == NullLogger.Instance)
{
Debug.WriteLine("No logger configured.....");
}
if (ObjectMapper == NullObjectMapper.Instance)
{
Debug.WriteLine("No mapper configured.....");
}
}
}
// OR domain service
public class MainService : DomainService, IMainService
{
public MainService(IObjectMapper mapper, ILogger logger)
{
if (Logger == NullLogger.Instance)
{
Debug.WriteLine("No logger configured.....");
}
if (ObjectMapper == NullObjectMapper.Instance)
{
Debug.WriteLine("No mapper configured.....");
}
}
}
Any ideas on how this can be resolved?
Thanks

I think, u have to register your injections. Something like this:
public class ServiceModule:AbpModule {
// other methods and logic
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddScoped<IMainService>(sp => sp.GetRequiredService<MainService>());
}
public override void PostInitialize(){
var mainService = Configuration.Get<IMainService>();
Task.Run(() =>
{
mainService.Start();
});
base.PostInitialize();
}
}

DomainService and ApplicationService allow IObjectMapper and ILogger to be property-injected.
If MainService requires non-null object pattern instances to work properly, you can proactively assign your constructor-injected instances:
public MainService(IObjectMapper mapper, ILogger logger)
{
ObjectMapper = mapper;
Logger = logger;
}
References:
https://aspnetboilerplate.com/Pages/Documents/Dependency-Injection#property-injection-pattern
https://aspnetboilerplate.com/Pages/Documents/Logging#getting-the-logger

Related

How to share Context data between MassTransit Consumers while using the `IBusControl` for publishing messages?

I am using MassTransit 7.2.2 in a .NET Core application with RabbitMQ(for local development) and SQS(for deployment) where a single message processing can result in multiple new messages getting created and processed.
All the messages share the same base type
public class BaseMessage : CorrelatedBy<Guid>
{
public BaseMessage()
{
CorrelationId = Guid.NewGuid();
CreationDate = DateTime.UtcNow;
}
public Guid CorrelationId { get; init; }
public DateTime CreationDate { get; }
public Guid? ConversationId { get; set; }
}
The basic flow of processing is same for all messages, there is a Service per Consumer.
public class FirstMessage : BaseMessage
{
}
public class FirstConsumer : IConsumer<FirstMessage>
{
private readonly ILogger<FirstConsumer> _logger;
private readonly FirstService _service;
public FirstConsumer(ILogger<FirstConsumer> logger, FirstService service)
{
_logger = logger;
_service = service;
}
public Task Consume(ConsumeContext<FirstMessage> context)
{
_logger.LogInformation($"FirstConsumer CorrelationId: {context.CorrelationId} and ConversationId: {context.ConversationId} and InitiatorId: {context.InitiatorId}");
_service.Process(context.Message);
return Task.CompletedTask;
}
}
public class FirstService
{
private readonly IBusControl _busControl;
private readonly ILogger<FirstService> _logger;
public FirstService(IBusControl busControl, ILogger<FirstService> logger)
{
_busControl = busControl;
_logger = logger;
}
public Task Process(FirstMessage firstMessage)
{
var secondMessage = new SecondMessage();
_busControl.Publish(secondMessage);
return Task.CompletedTask;
}
}
The above code is an example and the actual code base has 30+ consumers and all have the same pattern, i.e there is a Service per Consumer and the message is passed to the Service for processing.
I am trying to implement a solution for tracing messages end to end by using the Ids.
ConversationId - Unique Id for tracing logs of all Consumers in a graph
CorrelationId - Unique Id for tracing logs within a Consumer
InitiatorId - Parent Id
There is a message processing graph that looks like
FirstConsumer -> SecondConsumer -> ThirdConsumer.
I have the following Filters
ConsumeFilter
public class SimpleConsumeMessageFilter<TContext, TMessage> : IFilter<TContext>
where TContext : class, ConsumeContext<TMessage>
where TMessage : class
{
public SimpleConsumeMessageFilter()
{
}
public async Task Send(TContext context, IPipe<TContext> next)
{
LogContext.PushProperty("CorrelationId", context.CorrelationId);
LogContext.PushProperty("ConversationId", context.ConversationId);
LogContext.PushProperty("InitiatorId", context.InitiatorId);
await next.Send(context);
}
public void Probe(ProbeContext context)
{
context.CreateScope("consume-filter");
}
}
public class SimpleConsumeMessagePipeSpec<TConsumer, TMessage> : IPipeSpecification<ConsumerConsumeContext<TConsumer, TMessage>>
where TConsumer : class
where TMessage : class
{
public void Apply(IPipeBuilder<ConsumerConsumeContext<TConsumer, TMessage>> builder)
{
builder.AddFilter(new SimpleConsumeMessageFilter<ConsumerConsumeContext<TConsumer, TMessage>, TMessage>());
}
public IEnumerable<ValidationResult> Validate()
{
return Enumerable.Empty<ValidationResult>();
}
}
public class SimpleConsumePipeSpecObserver : IConsumerConfigurationObserver
{
public void ConsumerConfigured<TConsumer>(IConsumerConfigurator<TConsumer> configurator)
where TConsumer : class
{
}
public void ConsumerMessageConfigured<TConsumer, TMessage>(IConsumerMessageConfigurator<TConsumer, TMessage> configurator)
where TConsumer : class
where TMessage : class
{
configurator.AddPipeSpecification(new SimpleConsumeMessagePipeSpec<TConsumer, TMessage>());
}
}
PublishFilter
public class SimplePublishMessageFilter<TMessage> : IFilter<PublishContext<TMessage>> where TMessage : class
{
public SimplePublishMessageFilter()
{
}
public async Task Send(PublishContext<TMessage> context, IPipe<PublishContext<TMessage>> next)
{
if (context.Headers.TryGetHeader("ConversationId", out object #value))
{
var conversationId = Guid.Parse(#value.ToString());
context.ConversationId = conversationId;
}
else
{
if (context.Message is BaseMessage baseEvent && !context.ConversationId.HasValue)
{
context.ConversationId = baseEvent.ConversationId ?? Guid.NewGuid();
context.Headers.Set("ConversationId", context.ConversationId.ToString());
}
}
await next.Send(context);
}
public void Probe(ProbeContext context)
{
context.CreateScope("publish-filter");
}
}
public class SimplePublishMessagePipeSpec<TMessage> : IPipeSpecification<PublishContext<TMessage>> where TMessage : class
{
public void Apply(IPipeBuilder<PublishContext<TMessage>> builder)
{
builder.AddFilter(new SimplePublishMessageFilter<TMessage>());
}
public IEnumerable<ValidationResult> Validate()
{
return Enumerable.Empty<ValidationResult>();
}
}
public class SimplePublishPipeSpecObserver : IPublishPipeSpecificationObserver
{
public void MessageSpecificationCreated<TMessage>(IMessagePublishPipeSpecification<TMessage> specification)
where TMessage : class
{
specification.AddPipeSpecification(new SimplePublishMessagePipeSpec<TMessage>());
}
}
Added to config via
x.UsingRabbitMq((context, cfg) =>
{
cfg.ConnectConsumerConfigurationObserver(new SimpleConsumePipeSpecObserver());
cfg.ConfigurePublish(ppc =>
{
ppc.ConnectPublishPipeSpecificationObserver(new SimplePublishPipeSpecObserver());
});
cfg.UseDelayedMessageScheduler();
cfg.ConfigureEndpoints(context);
cfg.Host("localhost", rmq =>
{
rmq.Username("guest");
rmq.Password("guest");
});
});
With the above approach the 'CorrelationId' header is lost when the SecondConsumer's filters are run.
I have tried the following change and it seems to flow the Ids across the Consumers.
However, taking this approach will impact large sections of code / tests that rely on the IBusControl interface. I am keeping this as a backup option in case I can't find any other solution.
public class FirstService
{
private readonly ILogger<FirstService> _logger;
public FirstService(ILogger<FirstService> logger)
{
_logger = logger;
}
public Task Process( ConsumeContext<FirstMessage> consumeContext)
{
var secondMessage = new SecondMessage();
consumeContext.Publish(secondMessage);
return Task.CompletedTask;
}
}
Question: Is there a way to share the Context data between Consumers while using IBusControl for sending / publishing messages ?
Many thanks
As explained in the documentation, consumers (and their dependencies) must use one of the following when sending/publishing messages:
ConsumeContext, typically within the consumer itself
IPublishEndpoint or ISendEndpointProvider, typically used by scoped dependencies of the consumer
IBus, last resort, as all contextual data is lost from the inbound message
As for your final question, "Is there a way to share the Context data between Consumers while using IBusControl for sending / publishing messages?" the answer is no. The consume context would be needed to access any of the contextual data.

Injecting logger from DI to object created with factory

I need to add logger (ILogger) to existing object of MyDbConnection, this object is created from Factory which is registered in NET Core DI together with MyOptions class
public class MyFactory : IMyFactory
{
private readonly MyOptions _options;
public MyFactory(MyOptions options)
{
_options = options;
}
public MyDbConnection CreateDbA() => new MyDbConnection(_options.ConnStrA);
public MyDbConnection CreateDbB() => new MyDbConnection(_options.ConnStrB);
public MyDbConnection CreateDbC() => new MyDbConnection(_options.ConnStrC);
}
Factory is then injected into service, which then use it to create object and do something
public class MyService : IMyService
{
public MyService(IMyFactory factory)
{
var a = factory.CreateDbA();
var b = factory.CreateDbB();
a.DoSomething();
b.DoSomething();
}
}
MyDbConnection looks like this:
public class MyDbConnection
{
private string connStr;
//private ILogger logger;
//public MyDbConnection(string connStr, ILogger logger)
public MyDbConnection(string connStr)
{
this.connStr = connStr;
//this.logger = Logger;
}
public void DoSomething()
{
//logger.LogWarning();
}
}
Problem is that I can't just add ILogger and inject it from DI container because factory is using 'new' to create MyDbConnection, without using DI. My only solution currently is to use static logger instead of injected one, but that seems to be a bad solution. Is there another way around this? How it should be done properly?
The design will need to be refactored to be able to get the desired behavior.
First MyDbConnection should be refactored accordingly to depend on the appropriate logger
For example
public class MyDbConnection {
private string connStr;
private ILogger logger;
public MyDbConnection(string connStr, ILogger<MyDbConnection> logger) {
this.connStr = connStr;
this.logger = logger;
}
public void DoSomething() {
logger.LogWarning();
}
}
Then the factory refactored to use ActivatorUtilities to initialize and inject the necessary dependencies via an injected service provider.
public class MyFactory : IMyFactory {
private readonly MyOptions options;
private readonly IServiceProvider services;
public MyFactory(MyOptions options, IServiceProvider services) {
this.options = options;
this.services = services;
}
public MyDbConnection CreateDbA() => ActivatorUtilities.CreateInstance<MyDbConnection>(services, options.ConnStrA);
public MyDbConnection CreateDbB() => ActivatorUtilities.CreateInstance<MyDbConnection>(services, options.ConnStrB);
public MyDbConnection CreateDbC() => ActivatorUtilities.CreateInstance<MyDbConnection>(services, options.ConnStrC);
}
In the example above the specific dependencies are provided just as before when they were manually initialized. All other dependencies (like the logger) will be resolved via the service provider and injected into the target class when it is being initialized.
This way dependency injection can be maintained and manual initialization of MyDbConnection can be avoided.

C# Asp net core How to call database context outside of controllers

I have a question related to the use of database contexts outside the controller, namely, how to call the database context in a regular class?
To communicate with the database, I use: EF Core
I used options like this:
private readonly MSSQLContext _context;
public BookingController(MSSQLContext context)
{
_context = context;
}
Alternative
using (MSSQLContext context=new MSSQLContext())
{
context.get_Users.ToList();
}
Startup.cs
services.AddDbContext<MSSQLContext>(options =>
options.UseSqlServer(connection));
MSSQLContext.cs
public MSSQLContext()
{
}
public MSSQLContext(DbContextOptions<MSSQLContext> options)
: base(options)
{
}
public DbSet<VIVT_Core_Aud.Models.Core.Logger_Model> Logger_Models { get; set; }
and more tables...
Inject the context into whatever class you need to call into and register that class with the DI framework in your startup class.
For instance,
services.AddTransient<YourType>();
class YourType
{
public YourType(YourDbContext context) { ... }
}
You need to inject context using DI(dependency injection). I am showing you an example of repository pattern. You can search for "Repository pattern EF Core C# examples" will give you lots of examples or blogs to follow. Check here and here
Check out below example..
MyRepository.cs
namespace MyApp.Data.Services
{
public class MyRepository: IMyRepository, IDisposable
{
private MyAppContext _context;
private readonly ILogger<MyRepository> _logger;
public MyRepository(MyAppContext context, ILogger<MyRepository> logger)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_logger = logger;
}
public IEnumerable<MyTable> GetMyTableData()
{
return _context.MyTable.ToList();
}
}
}
IMyRepository.cs
namespace MyApp.Data.Services
{
public interface IMyRepository
{
//Interface implementation
IEnumerable<MyTable> GetMyTableData();
}
}
Startup.cs of MVC project
services.AddDbContext<MyAppContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("AppDbConnString")));
//Scope of repository should be based on your project used, I recommend to check lifetime & scope based your project needs.
services.AddScoped<IMyRepository, MyRepository>();
Mycontroller.cs
using MyApp.Data.Services;
namespace MyApp.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly IMyRepository _myRepository;
public HomeController(ILogger<HomeController> logger, IMyRepository myRepository)
{
_logger = logger;
_myRepository = myRepository ??
throw new ArgumentNullException(nameof(myRepository));
}
public IActionResult Index()
{
return View();
}
public IActionResult GetAllData()
{
var result = _myRepository.GetMyTableData();
return Json(result);
}
}
}

unit test base controller with ILogger

I have a base controller (i didn't create it btw) in my net core api that basically starts with following:
public abstract class MyBaseController<T> : ControllerBase where T : MyBaseController<T>
{
private ILogger<T> _logger;
protected ILogger<T> Logger => _logger ?? (_logger = HttpContext?.RequestServices.GetService<ILogger<T>>());
}
When i am unit testing my other controller that inherits the base controller how do deal with this logger?
currently my unit test class has a constructer with something like
_controller = new cartController(_cartService);
but then i get stuck.
I will be using xUnit and Moq in the test project.
Any help is appreciated.Thanks
Here's a minimal example from this article on how to inject an ILogger dependency and then verify a call afterward with Moq:
public class LogTest
{
private readonly ILogger _logger;
public const string InformationMessage = "Test message";
public const string ErrorMessage = "Not implemented {recordId}";
public LogTest(ILogger<LogTest> logger)
{
_logger = logger;
}
public void Process()
{
_logger.LogInformation(InformationMessage);
}
}
_loggerMock.Verify(l => l.Log(
LogLevel.Information,
It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(),
It.IsAny<Exception>(),
(Func<It.IsAnyType, Exception, string>)It.IsAny<object>()), Times.Exactly(1));
In general you should rely on DI in tests as well as in runtime. The following library contains a test logger which you may use in tests: https://www.nuget.org/packages/com.github.akovac35.Logging.Testing/
Usage samples are available here: https://github.com/akovac35/Logging.Samples
Disclaimer: I am the author of the above.
Basically you would proceed as follows:
Use NullLogger by default:
public abstract class MyBaseController<T> : ControllerBase
{
private ILogger _logger = NullLogger.Instance;
protected MyBaseController(ILogger<MyBaseController<T>> logger = null)
{
if (logger != null) _logger = logger;
}
}
Derived classes should inject logger:
public class MyBaseControllerVariant<T> : MyBaseController<T>
{
private ILogger _logger = NullLogger.Instance;
public MyBaseControllerVariant(ILogger<MyBaseControllerVariant<T>> logger = null, ILogger<MyBaseController<T>> baseLogger = null): base(baseLogger)
{
if (logger != null) _logger = logger;
}
}
Now wire up everything:
using com.github.akovac35.Logging.Testing;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Shared.Mocks;
using System;
namespace TestApp
{
[TestFixture]
public class TestLoggingExamples
{
[OneTimeSetUp]
public void OneTimeSetUp()
{
customOnWrite = writeContext => {
Console.WriteLine(writeContext);
};
customOnBeginScope = scopeContext => {
Console.WriteLine(scopeContext);
};
serviceCollection = new ServiceCollection();
serviceCollection.AddTransient(typeof(MyBaseControllerVariant<>));
// Register TestLogger using extension method
serviceCollection.AddTestLogger(onWrite: customOnWrite, onBeginScope: customOnBeginScope);
}
private IServiceCollection serviceCollection;
private Action<WriteContext> customOnWrite;
private Action<ScopeContext> customOnBeginScope;
[Test]
public void Test_WithLoggingToTestConsole_Works()
{
// The service provider should be defined on per-test level or logger writes will accumulate and may result in OOM - clean them with testSink.Clear()
var serviceProvider = serviceCollection.BuildServiceProvider();
var controller = serviceProvider.GetRequiredService<MyBaseControllerVariant<object>>();
controller.Invoke();
var testSink = serviceProvider.GetRequiredService<ITestSink>();
Assert.IsTrue(testSink.Writes.Count > 0);
Assert.IsTrue(testSink.Scopes.Count > 0);
}
}
}

EF 7 (Core). Create DBContext like AddTransient

According to documents when I configure DbContext like below DI register it in scope (per http request)
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<DBData>(options => {
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]);
}
);
The problem appears when I am trying to access it in another thread.
public class HomeController : Controller
{
private readonly DBData _context;
public HomeController(DBData context)
{
_context = context;
}
public IActionResult StartInBackground()
{
Task.Run(() =>
{
Thread.Sleep(3000);
//System.ObjectDisposedException here
var res = _context.Users.FirstOrDefault(x => x.Id == 1);
});
return View();
}
}
I want to configure DbContext creation per each call (AddTransition). It would give me possibility to write next code
public void ConfigureServices(IServiceCollection services)
{
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<DBData>(options => {
//somehow configure it to use AddTransient
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]);
}
);
services.AddTransient<IUnitOfWorkFactoryPerCall, UnitOfWorkFactory>();
services.AddScoped<IUnitOfWorkFactoryPerRequest, UnitOfWorkFactory>();
services.AddMvc();
}
public interface IUnitOfWorkFactoryPerCall : IUnitOfWorkFactory { }
public interface IUnitOfWorkFactoryPerRequest : IUnitOfWorkFactory { }
public interface IUnitOfWorkFactory : IDisposable
{
DBData Context { get; }
}
public class UnitOfWorkFactory : IUnitOfWorkFactoryPerCall, IUnitOfWorkFactoryPerRequest
{
public UnitOfWorkFactory(DBData context)
{
Context = context;
}
public DBData Context
{
get; private set;
}
public void Dispose()
{
Context.Dispose();
}
}
So now if I want to create DBContext per request I will use IUnitOfWorkFactoryPerRequest, and when I want to use DBContext in some background thread I can use IUnitOfWorkFactoryPerCall.
My temporary solution.
I created singleton which can create Context "in transient way"
public class AppDependencyResolver
{
private static AppDependencyResolver _resolver;
public static AppDependencyResolver Current
{
get
{
if (_resolver == null)
throw new Exception("AppDependencyResolver not initialized. You should initialize it in Startup class");
return _resolver;
}
}
public static void Init(IServiceProvider services)
{
_resolver = new AppDependencyResolver(services);
}
private readonly IServiceProvider _serviceProvider;
public AppDependencyResolver(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IUnitOfWorkFactory CreateUoWinCurrentThread()
{
var scopeResolver = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope();
return new UnitOfWorkFactory(scopeResolver.ServiceProvider.GetRequiredService<DBData>(), scopeResolver);
}
}
Then I call init method in Startup Configure method
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
AppDependencyResolver.Init(app.ApplicationServices);
//other configure code
}
And after all I can call AppDependencyResolver.Current.CreateUoWinCurrentThread() in some background thread.
If someone can provide more elegant solution I will be appreciated.
Within your controller, why are you trying to inject into private readonly DBData _context;? If you've registered your IUnitOfWorkFactoryPerCall via DI, you should be injecting that into your controller I believe? You then access your context via the interface.
To expand, this is what I am suggesting you do:
public class HomeController : Controller
{
private readonly IUnitOfWorkFactoryPerCall _contextFactory;
public HomeController(IUnitOfWorkFactoryPerCall contextFactory)
{
_contextFactory = contextFactory;
}
public IActionResult StartInBackground()
{
Task.Run(() =>
{
Thread.Sleep(3000);
//System.ObjectDisposedException here
var res = _contextFactory.Context.Users.FirstOrDefault(x => x.Id == 1);
});
return View();
}
}

Categories

Resources