I'm struggling to get dependency injection working in a none controller class in ASP.NET 5.
I'm trying to inject an instance of IHelloMessage in ResponseWriter.
I have the following code in startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IHelloMessage, HelloMessage>();
}
public void Configure(IApplicationBuilder app)
{
app.Run(async context =>
{
await Task.Run(() =>
{
new ResponseWriter().Write(context);
});
});
}
I have the following code in ResponseWriter.cs:
public class ResponseWriter
{
public IHelloMessage HelloMessage { get; set; }
public ResponseWriter()
{
}
public ResponseWriter(IHelloMessage helloMessage)
{
HelloMessage = helloMessage;
}
public void Write(HttpContext HttpContext)
{
HttpContext.Response.WriteAsync(HelloMessage.Text);
}
}
and here's the code for HelloMessage:
public interface IHelloMessage
{
string Text { get; set; }
}
public class HelloMessage : IHelloMessage
{
public string Text { get; set; }
public HelloMessage()
{
Text = "Hello world at " + DateTime.Now.ToString();
}
}
When I run the app, I get the following error:
I'm sure I'm missing something silly - any help would be appreciated!
You are calling your parameter less constructor: new ResponseWriter().Write(context); so your HelloMessage is null.
If you want to use dependency injection you must use IAppBuilder.ApplicationService.GetService or IAppBuilder.ApplicationService.GetRequiredService methods
Your statup.cs can be:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IHelloMessage, HelloMessage>();
}
public void Configure(IApplicationBuilder app)
{
app.Run(async context =>
{
await Task.Run(() =>
{
new ResponseWriter(app.ApplicationServices.GetRequiredService<IHelloMessage>()).Write(context);
});
});
}
Related
I am trying to figure why "Sending" a message does not invoke state machine, but if I "Publish" a message, it works and I can see the state changing.
Following is my code, it is similar to the documentation, except that I am trying to "Send" a message.
Components
State Machine:
public class OrderState: SagaStateMachineInstance
{
public Guid CorrelationId { get; set; }
public int CurrentState { get; set; }
public DateTime? OrderDate { get; set; }
}
public class OrderStateMachine: MassTransitStateMachine<OrderState>
{
public State Submitted { get; private set; }
public State Accepted { get; private set; }
public State Completed { get; private set; }
public Event<SubmitOrder> SubmitOrder { get; private set; }
public Event<OrderAccepted> OrderAccepted { get; private set; }
public Event<OrderCompleted> OrderCompleted { get; private set; }
public OrderStateMachine()
{
InstanceState(x => x.CurrentState, Submitted, Accepted, Completed);
Event(() => SubmitOrder, x => x.CorrelateById(context => context.Message.OrderId));
Event(() => OrderAccepted, x => x.CorrelateById(context => context.Message.OrderId));
Event(() => OrderCompleted, x => x.CorrelateById(context => context.Message.OrderId));
Initially(
When(SubmitOrder)
.Then(context => context.Instance.OrderDate = context.Data.OrderDate)
.TransitionTo(Submitted));
During(Submitted,
When(OrderAccepted)
.TransitionTo(Accepted));
During(Accepted,
Ignore(SubmitOrder));
DuringAny(
When(OrderCompleted)
.TransitionTo(Completed));
SetCompleted(async instance =>
{
var currentState = await this.GetState(instance);
return Completed.Equals(currentState);
});
}
}
Contracts:
public record SubmitOrder(Guid OrderId, DateTime? OrderDate);
public record OrderAccepted(Guid OrderId);
public record OrderCompleted(Guid OrderId);
Consumers:
public class SubmitOrderConsumer: IConsumer<SubmitOrder>
{
public async Task Consume(ConsumeContext<SubmitOrder> context)
{
await Task.Delay(2000);
}
}
public class SubmitOrderConsumerDefinition : ConsumerDefinition<SubmitOrderConsumer>
{
public SubmitOrderConsumerDefinition()
{
EndpointName = "submit-order";
}
protected override void ConfigureConsumer(IReceiveEndpointConfigurator endpointConfigurator, IConsumerConfigurator<SubmitOrderConsumer> consumerConfigurator)
{
endpointConfigurator.ConfigureConsumeTopology = false;
}
}
Web API
Program.cs (snippet)
// Add services to the container.
builder.Services.AddMassTransit(cfg =>
{
cfg.SetKebabCaseEndpointNameFormatter();
cfg.UsingRabbitMq((context, configurator) =>
{
configurator.Host("localhost", "/", hostConfigurator =>
{
hostConfigurator.Username("guest");
hostConfigurator.Password("guest");
});
});
});
builder.Services.AddMassTransitHostedService();
builder.Services.AddControllers();
OrderController
[Route("order")]
public class OrderController : ControllerBase
{
private readonly ISendEndpointProvider _sendEndpointProvider;
public OrderController(ISendEndpointProvider sendEndpointProvider)
{
_sendEndpointProvider = sendEndpointProvider;
}
[HttpPost]
public async Task<IActionResult> SendOrder()
{
var endpoint = await _sendEndpointProvider.GetSendEndpoint(new Uri("exchange:submit-order"));
await endpoint.Send(new SubmitOrder(Guid.NewGuid(), DateTime.Now));
return Ok();
}
}
Worker Service
Program.cs
using IHost = Microsoft.Extensions.Hosting.IHost;
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddMassTransit(cfg =>
{
cfg.AddConsumer<SubmitOrderConsumer>(typeof(SubmitOrderConsumerDefinition));
cfg.AddSagaStateMachine<OrderStateMachine, OrderState>().InMemoryRepository();
cfg.UsingRabbitMq((context, rabbitMqConfigurator) =>
{
rabbitMqConfigurator.Host("localhost", "/", hostConfigurator =>
{
hostConfigurator.Username("guest");
hostConfigurator.Password("guest");
});
rabbitMqConfigurator.ReceiveEndpoint("saga-order", endpointConfigurator =>
{
endpointConfigurator.ConfigureSaga<OrderState>(context);
});
rabbitMqConfigurator.ConfigureEndpoints(context);
});
});
services.AddMassTransitHostedService();
services.AddHostedService<Worker>();
})
.Build();
await host.RunAsync();
Then I do a POST via Postman to: http://localhost:5000/order
It does call the SubmitOrderConsumer, but for some reason, the State machine does not get invoked (it won't hit breakpoint inside the Then handler that sets the Order Date inside Initially state.). I think I am missing something that connects the two together.
Any feedback is greatly appreciated. Thank you.
In your example, you'd want to use Publish, especially in this scenario where you have two consumers (the consumer, and the state machine) on separate endpoints (queue) that would be consuming the message. Sending directly to the exchange would only get the message to one of the endpoints.
I am following a tutorial about MediatR in .NET Web API, and facing this error :
System.AggregateException: 'Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: MediatR.IRequestHandler`2[Application.Activities.List+Query,System.Collections.Generic.List`1[Domain.Activity]] Lifetime: Transient ImplementationType: Application.Activities.List+Handler': Unable to resolve service for type 'Domain.DataContext' while attempting to activate 'Application.Activities.List+Handler'.)'
I have no idea what it is. Here is my code:
Activity Class:
public class Activity
{
public Guid Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Category { get; set; }
public DateTime Date { get; set; }
public string City { get; set; }
public string Venue { get; set; }
}
DataContext.cs
public class DataContext : DbContext
{
public DbSet<Value> Values { get; set; }
public DbSet<Activity> Activities { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("server=localhost;database=Reactivities;uid=admin2;pwd=minhquan");
}
}
List.cs
public class List
{
public class Query: IRequest<List<Activity>> { }
public class Handler : IRequestHandler<Query, List<Activity>>
{
private readonly DataContext context;
public Handler(DataContext context)
{
this.context = context;
}
public async Task<List<Activity>> Handle(Query request, CancellationToken cancellationToken)
{
var activities = await context.Activities.ToListAsync();
return activities;
}
}
}
Program.cs
public class Program
{
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using(var scope = host.Services.CreateScope())
{
var service = scope.ServiceProvider;
try
{
var context = service.GetRequiredService<DataContext>();
context.Database.Migrate();
}
catch(Exception ex)
{
var logger = service.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error has occured during migration");
}
}
host.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(opt => {
opt.AddPolicy("CorsPolicy", policy =>
{
policy.AllowAnyHeader().AllowAnyMethod().WithOrigins("http://localhost:3000");
});
});
services.AddMediatR(typeof(List.Handler).Assembly);
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "API", Version = "v1" });
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "API v1"));
}
app.UseRouting();
app.UseAuthorization();
app.UseCors("CorsPolicy");
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
Whenever I run the program, It gets stuck at the var host = CreateHostBuilder(args).Build(); followed by the error
Looking at the error message further, you will notice the following sentence:
Unable to resolve service for type 'Domain.DataContext' while attempting to activate 'Application.Activities.List+Handler'
What this is saying is the dependency injection setup in Startup.cs has no idea on how to instantiate Domain.DataContext.
Add services.AddDbContext<DataContext>(); before the services.AddMediatR(typeof(List.Handler).Assembly); call in ConfigureServices.
For more information on how to setup Entity Framework Core, please visit the MSDN tutorial on it.
I am trying to receive an event from the RabbitMQ broker but something wents wrong, the Consume method of my consumer is never called, although the message is visible on the bus. Here's my IntegrationEvent class:
public abstract class IntegrationEvent
{
protected IntegrationEvent(Guid entityId,
string eventType)
{
EntityId = entityId;
EventType = eventType;
}
public Guid Id { get; } = Guid.NewGuid();
public DateTime CreatedAtUtc { get; } = DateTime.UtcNow;
public Guid EntityId { get; }
public string EventType { get; }
public DateTime? PublishedAtUtc { get; set; }
}
And the example inheritor:
public sealed class UserCreatedIntegrationEvent : IntegrationEvent
{
public UserCreatedIntegrationEvent(Guid id,
string login,
string firstName,
string lastName,
string mailAddress)
: base(id,
nameof(UserCreatedIntegrationEvent))
{
Login = login;
FirstName = firstName;
LastName = lastName;
MailAddress = mailAddress;
}
public string Login { get; }
public string FirstName { get; }
public string LastName { get; }
public string MailAddress { get; }
}
Publication logic:
public async Task PublishAsync(params IntegrationEvent[] events)
{
var globalPublicationTasks = events
.Select(#event =>
{
#event.PublishedAtUtc = DateTime.UtcNow;
return _publishEndpoint.Publish(#event);
});
await Task.WhenAll(globalPublicationTasks);
}
Receiver classes and the dependencies registry code:
public sealed class IntegrationEventListener : BackgroundService
{
public IntegrationEventListener(IBusControl busControl,
IServiceProvider serviceProvider,
IOptions<RabbitMQSettings> busConfiguration)
: base(busControl,
serviceProvider,
busConfiguration,
NullLogger.Instance)
{
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
try
{
var handler = BusControl
.ConnectReceiveEndpoint(BusConfiguration.HostName, receiveEndpointConfigurator =>
{
receiveEndpointConfigurator
.Consumer<IntegrationEventTransmitter>(ServiceProvider);
});
await handler.Ready;
}
catch (Exception e)
{
...
}
}
}
public sealed class IntegrationEventTransmitter : IntegrationEventHandler<IntegrationEvent>
{
public override async Task HandleAsync(IntegrationEvent #event)
{
throw new System.NotImplementedException();
}
}
public abstract class IntegrationEventHandler<TIntegrationEvent>
: IIntegrationEventHandler<TIntegrationEvent>,
IConsumer<TIntegrationEvent>
where TIntegrationEvent : IntegrationEvent
{
public async Task Consume(ConsumeContext<TIntegrationEvent> context) =>
await HandleAsync(context.Message);
public abstract Task HandleAsync(TIntegrationEvent #event);
}
...
.AddRabbitMQ(configuration,
ExchangeType.Fanout,
true)
.AddScoped<IntegrationEventTransmitter>()
.AddHostedService<IntegrationEventListener>();
...
internal static IServiceCollection RegisterRabbitMQDependencies(
this IServiceCollection services,
IConfiguration configuration,
string exchangeType)
{
var rabbitMQSettings = configuration
.GetSection(RabbitMQSettingsSectionKey)
.Get<RabbitMQSettings>();
services
.AddMassTransit(configurator =>
{
configurator.AddConsumers(typeof(IntegrationEventHandler<IntegrationEvent>).Assembly);
})
.AddSingleton(serviceProvider => MassTransit.Bus.Factory.CreateUsingRabbitMq(configurator =>
{
configurator
.Host(rabbitMQSettings.HostName,
rabbitMQSettings.VirtualHostName,
hostConfigurator =>
{
hostConfigurator.Username(rabbitMQSettings.UserName);
hostConfigurator.Password(rabbitMQSettings.Password);
});
configurator.ExchangeType = exchangeType;
}))
.AddSingleton<IPublishEndpoint>(provider => provider.GetRequiredService<IBusControl>())
.AddSingleton<ISendEndpointProvider>(provider => provider.GetRequiredService<IBusControl>())
.AddSingleton<IBus>(provider => provider.GetRequiredService<IBusControl>())
.Configure<RabbitMQSettings>(configuration.GetSection(RabbitMQSettingsSectionKey));
return services;
}
In the RabbitMQ management panel i can notice that message is being properly published on the bus, the consumer is also connected to the broker but for some reason it does not consume the message. What am i doing wrong?
You should not connect a receiving endpoint, as it's completely unnecessary in this case. As Chris mentioned, configuring MassTransit for ASP.NET Core is properly described in the documentation, and it makes total sense to follow the documentation to avoid unnecessary complexity.
In your particular case, you don't start the bus, although it's even mentioned in the Common Mistakes article as the first thing.
Just do the following:
Use AddMassTransit in Startup and configure the receive endpoint normally
Add the handler directly there, or use a consumer class instead. It does not need to be a background service, MassTransit will call it when it receives a message
Register the MassTransit host by calling AddMassTransitHostedService
I have a problem with Autofac injection or registration.
This is my code
Repository
namespace ClientConfiguration.Data.Repository
{
public class MappingBaseRepository : RepositoryBase<MappingBase>, IMappingBaseRepository
{
public MappingBaseRepository(IDatabaseFactory databaseFactory)
: base(databaseFactory)
{
}
}
public interface IMappingBaseRepository : IRepository<MappingBase>
{
}
}
Service
namespace ClientConfiguration.Service {
public interface IMappingBaseService
{
IEnumerable<MappingBase> GetElements();
void SaveElement();
}
public class MappingBaseService : IMappingBaseService
{
private readonly IMappingBaseRepository MappingBaseRepository;
private readonly IUnitOfWork unitOfWork;
public MappingBaseService(IMappingBaseRepository MappingBaseRepository, IUnitOfWork unitOfWork)
{
this.MappingBaseRepository = MappingBaseRepository;
this.unitOfWork = unitOfWork;
}
#region Members
public IEnumerable<MappingBase> GetElements()
{
var Elements = MappingBaseRepository.GetAll();
return Elements;
}
public void SaveElement()
{
unitOfWork.Commit();
}
#endregion
} }
Autofac init
private static void SetAutofacContainer() {
var builder = new ContainerBuilder();
builder.RegisterControllers(Assembly.GetExecutingAssembly());
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerRequest();
builder.RegisterType<DatabaseFactory>().As<IDatabaseFactory>().InstancePerRequest();
// Repositories
builder.RegisterAssemblyTypes(typeof(ClientElementRepository).Assembly)
.Where(t => t.Name.EndsWith("Repository"))
.AsImplementedInterfaces().InstancePerRequest();
// Services
builder.RegisterAssemblyTypes(typeof(ClientElementService).Assembly)
.Where(t => t.Name.EndsWith("Service"))
.AsImplementedInterfaces().InstancePerRequest();
IContainer container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
Now if I'm inside a controller I have an instance of the service objects without problem. But I have to access my service IMappingBaseService to get data from DB inside this class:
namespace ClientConfiguration.Mappings {
public class CustomDisplayNameAttribute : DisplayNameAttribute {
private static IMappingBaseService mappingBaseService { get; set; }
public CustomDisplayNameAttribute(string value)
: base(GetMessageFromResource(value)) {
}
private static string GetMessageFromResource(string value) {
var els = mappingBaseService.GetElements().ToList();
//get value from DB
//mappingBaseService is always null
return "";
}
}
}
Any help would be greatly appreciated! Thanks in advance.
Code demo such as:
namespace ClientConfiguration.Mappings {
public class CustomDisplayNameAttribute : DisplayNameAttribute {
private static IMappingBaseService _mappingBaseService { get; set; }
public CustomDisplayNameAttribute(string value, IMappingBaseService mappingBaseService)
: base(GetMessageFromResource(value, mappingBaseService)) {
}
private static string GetMessageFromResource(string value, IMappingBaseService mappingBaseService) {
_mappingBaseService = mappingBaseService;
var els = _mappingBaseService .GetElements().ToList();
//OR var els = mappingBaseService.GetElements().ToList();
//get value from DB
//mappingBaseService is always null
return "";
}
}
}
Maybe you can fix code register autofac, Because autofac only register for interface such as:
builder.RegisterAssemblyTypes(typeof(IClientElementRepository).Assembly)
.Where(t => t.Name.EndsWith("Repository"))
.AsImplementedInterfaces().InstancePerRequest();
// Services
builder.RegisterAssemblyTypes(typeof(IClientElementService).Assembly)
.Where(t => t.Name.EndsWith("Service"))
.AsImplementedInterfaces().InstancePerRequest();
builder.RegisterAssemblyTypes(typeof(IMappingBaseService).Assembly)
.Where(t => t.Name.EndsWith("Service"))
.AsImplementedInterfaces().InstancePerRequest();
The solution was to use Property injection (instanciate the class inside the autofac init)
We have to add this line
builder.Register(c => new CustomDisplayNameAttribute {
_mappingBaseService = c.Resolve<IMappingBaseService>() });
and in CustomDisplayNameAttribute we add empty constructor
public CustomDisplayNameAttribute() {}
and
public IMappingBaseService _mappingBaseService { get; set; }
and for getting the object we use
var _mappingBaseService = DependencyResolver.Current.GetService<IMappingBaseService>();
The problem is that is i surcharge CustomDisplayName from DisplayNameAttribute (ASP.NET MVC)
public class ClientElementsViewModel {
private static IMappingBaseService _mappingBaseService;
public ClientElementsViewModel(IMappingBaseService mappingBaseService) {
_mappingBaseService = mappingBaseService;
}
[Key]
[Display(Name = "Id")]
public long ClientElementId { get; set; }
[CustomDisplayName("", _mappingBaseService)]
public string CompanyCode { get; set; }
//[CustomDisplayName("")]
public string WebAppBaseUrl { get; set; }
//[CustomDisplayName("")]
public string GuestTraveller { get; set; }
}
I have this error
Error 3 An attribute argument must be a constant expression, typeof
expression or array creation expression of an attribute parameter
type D:\CDS_ADMIN\ClientConfiguration.Web\ViewModel\ClientElementsViewModel.cs 22 32 ClientConfiguration.Web
First time using MS Unity. I have a controller with the following constructor:
protected IAdministrationService AdministrationService { get; set; }
public GenerateCacheController(IAdministrationService administrationService)
{
AdministrationService = administrationService;
}
I get the following error when trying to run the project:
Make sure that the controller has a parameterless public constructor.
In my Bootstrpper.cs file I have the following in the RegisterTypes method:
container.RegisterType<GenerateCacheController>();
I still get the error. Am I missing anything else? I'm using ASP.NET MVC 5 and Unity 3.
Here's my Boostrapper.cs file:
public static class Bootstrapper
{
public static IUnityContainer Initialise()
{
var container = BuildUnityContainer();
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
return container;
}
private static IUnityContainer BuildUnityContainer()
{
var container = new UnityContainer();
RegisterTypes(container);
return container;
}
public static void RegisterTypes(IUnityContainer container)
{
container.RegisterInstance(container);
var im = new InjectionMember[0];
container.RegisterType<IAdministrationService, AdministrationService>("AdministrationService", im);
container.RegisterType<ILookupMapper, LookupMapper>("LookupMapper", im);
container.RegisterType<IEmailService, EmailService>("EmailService", im);
container.RegisterType<GenerateCacheController>();
var provider = new UnityServiceLocator(container);
ServiceLocator.SetLocatorProvider(() => provider);
}
}
Abbreviated version of the AdministrationService class:
public class AdministrationService : IAdministrationService
{
protected ILookupMapper LookupMapper { get; set; }
protected IEmailService EmailService { get; set; }
public AdministrationService(ILookupMapper lookupMapper, IEmailService emailService)
{
LookupMapper = lookupMapper;
EmailService = emailService;
}
}
Found the issue.
I commented out the line:
var im = new InjectionMember[0];
container.RegisterType<IAdministrationService, AdministrationService>("AdministrationService", im);
and added:
container.RegisterType<IAdministrationService, AdministrationService>();
And that worked because the previous developers were doing something like this:
private IUnityContainer Container { get; set; }
public AdministrationService()
{
Container = Microsoft.Practices.ServiceLocation.ServiceLocator.Current.GetInstance<IUnityContainer>();
}
instead of
protected ILookupMapper LookupMapper { get; set; }
protected IEmailService EmailService { get; set; }
public AdministrationService(ILookupMapper lookupMapper, IEmailService emailService)
{
LookupMapper = lookupMapper;
EmailService = emailService;
}
I have to go back to their way to not break existing code. I'll get around to refactoring one day.