How to use dependency injection in consumer class. I get error message as "The type 'Esb.Masstransit.RabbitMq.Application.IEventConsumerService' cannot be used as type parameter 'TConsumer' in the generic type or method 'MassTransit.ConsumerExtensions.Consumer(MassTransit.IReceiveEndpointConfigurator, System.Action<MassTransit.ConsumeConfigurators.IConsumerConfigurator>)'. There is no implicit reference conversion from 'Esb.Masstransit.RabbitMq.Application.IEventConsumerService' to 'MassTransit.IConsumer'"
This is container class
container.Register(Component.For<IEventConsumerService>().ImplementedBy<EventConsumerService>().LifestyleSingleton());
container.Register(Component.For<ICommandConsumerService>().ImplementedBy<CommandConsumerService>().LifestyleSingleton());
var busControl = Bus.Factory.CreateUsingRabbitMq(rabbit =>
{
var host = rabbit.Host(new Uri(rabbitMqHost), settings =>
{
settings.Password(rabbitMqUserName);
settings.Username(rabbitMqPassword);
});
rabbit.ReceiveEndpoint(host, eventQueue1, e =>
{
e.BindMessageExchanges = false;
e.Consumer<EventConsumerService>(); // I getting error here
e.Bind(typeof(IPublishMessage).FullName.Split('.').LastOrDefault(), x =>
{
x.RoutingKey = "1";
x.ExchangeType = ExchangeType.Direct;
});
});
});
container.Register(Component.For<IBus, IBusControl>().Instance(busControl));
This is Consumer class
public class EventConsumerService : BaseLogger, IEventConsumerService, IConsumer<IPublishMessage>
{
private readonly ICommandConsumerService _service;
public EventConsumerService(ICommandConsumerService service)
{
_service = service;
}
public Task Consume(ConsumeContext<IPublishMessage> context)
{
try
{
Console.WriteLine("In event consumer " + context.MessageId + " _" + context.Message.MessageId + ", " + context.Message.Message + " " + context.Message.CreationDate);
return Task.CompletedTask;
}
catch (Exception ex)
{
Logger.Error("EventConsumerService", ex);
}
return Task.CompletedTask;
}
}
You should use the container integration package, MassTransit.CastleWindsor and configure your container as shown in the documentation.
Also, consumers should be registered as concrete types, not interfaces.
Based upon your example:
var container = new WindsorContainer();
container.AddMassTransit(x =>
{
x.AddConsumer<EventConsumerService>();
x.UsingRabbitMq(cfg =>
{
cfg.Host(new Uri(rabbitMqHost), settings =>
{
settings.Password(rabbitMqUserName);
settings.Username(rabbitMqPassword);
});
cfg.ReceiveEndpoint(eventQueue1, ec =>
{
ec.ConfigureConsumeTopology = false;
ec.Bind(typeof(IPublishMessage).FullName.Split('.').LastOrDefault(), b =>
{
b.RoutingKey = "1";
b.ExchangeType = ExchangeType.Direct;
});
ec.ConfigureConsumer<EventConsumerService>(context);
});
});
});
Consumers should not be singleton, they should be scoped. The code above will register the consumer in the container for you.
Related
I have three projects. One is Dot net core MVC, two are API projects. MVC is calling one API for user details. When user details are asked, I am sending message to queue through MassTransit. I am seeing skipped queue. There's consumer in third project which is API project.
I tried to make another solution for a demo with same configuration. It's running fine.
Below is MVC Razor page code..
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
ReturnUrl = returnUrl;
if (ModelState.IsValid)
{
var user = await AuthenticateUser(Input.Email);
if (user == null)
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Page();
}
#region snippet1
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.Email),
new Claim("FullName", user.FullName),
new Claim(ClaimTypes.Role, "Administrator"),
};
var claimsIdentity = new ClaimsIdentity(
claims, CookieAuthenticationDefaults.AuthenticationScheme);
var authProperties = new AuthenticationProperties
{
ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(15),
IsPersistent = true,
};
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
authProperties);
#endregion
_logger.LogInformation("User {Email} logged in at {Time}.",
user.Email, DateTime.UtcNow);
return LocalRedirect(Url.GetLocalUrl(returnUrl));
}
return Page();
}
private async Task<ApplicationUser> AuthenticateUser(string email)
{
if (!string.IsNullOrEmpty(email))
{
using (var client = new System.Net.Http.HttpClient())
{
var request = new System.Net.Http.HttpRequestMessage();
request.RequestUri = new Uri("http://localhost:52043/api/user?uName=" + email); // ASP.NET 3 (VS 2019 only)
var response = await client.SendAsync(request);
var customer = Newtonsoft.Json.JsonConvert.DeserializeObject<Customers>(response.Content.ReadAsStringAsync().Result);
return new ApplicationUser()
{
Email = email,
FullName = customer.FullName
};
}
}
else
{
return null;
}
}
MVC Startup:
services.AddMassTransit(x =>
{
x.AddBus(provider => Bus.Factory.CreateUsingRabbitMq(cfg =>
{
// configure health checks for this bus instance
cfg.UseHealthCheck(provider);
cfg.Host("rabbitmq://localhost");
}));
});
services.AddMassTransitHostedService();
User API Code - 52043:
[HttpGet]
public async Task<IActionResult> Get(string uName)
{
var customer = _userRepository.GetCustomerByUserName(uName);
Uri uri = new Uri("rabbitmq://localhost/loginqueue");
var endpoint = await _bus.GetSendEndpoint(uri);
await endpoint.Send(new LoginObj() { NoteString = customer.FullName + " has logged in at " + DateTime.Now.ToString() });
return Json(customer);
}
Logging API - Consumer Code:
public class LoginConsumer : IConsumer<LoginObj>
{
private readonly ILogger<object> _logger;
public LoginConsumer(ILogger<object> logger)
{
_logger = logger;
}
public async Task Consume(ConsumeContext<LoginObj> context)
{
var data = context.Message;
_logger.LogInformation(data.ToString());
}
}
Login API Startup:
services.AddMassTransit(x =>
{
x.AddConsumer<LoginConsumer>();
x.AddBus(provider => Bus.Factory.CreateUsingRabbitMq(cfg =>
{
// configure health checks for this bus instance
cfg.UseHealthCheck(provider);
cfg.Host("rabbitmq://localhost");
cfg.ReceiveEndpoint("loginqueue", ep =>
{
ep.PrefetchCount = 16;
ep.UseMessageRetry(r => r.Interval(2, 100));
ep.ConfigureConsumer<LoginConsumer>(provider);
});
}));
});
services.AddMassTransitHostedService();
As per the documentation:
MassTransit uses the full type name, including the namespace, for message contracts. When creating the same message type in two separate projects, the namespaces must match or the message will not be consumed.
Make sure that your message type has the same namespace/type in each project.
I am using global configuration for Automapper profile mapping.
public class StudentProfile : Profile
{
public StudentProfile()
{
CreateMap<Student, StudentVM>()
.ForMember(dest => dest.school, src => src.Ignore());
}
}
Mapper Configuration
public static class Configuration
{
public static IMapper InitializeAutoMapper()
{
MapperConfiguration config = new MapperConfiguration(cfg =>
{
cfg.AddProfile(new StudentProfile());
});
config.AssertConfigurationIsValid();
return config.CreateMapper();
}
}
Now I am adding .AddAfterMapAction using Expression.
static void Main(string[] args)
{
try
{
var mapper = Configuration.InitializeAutoMapper();
foreach (var item in mapper.ConfigurationProvider.GetAllTypeMaps())
{
Expression<Action<int>> beforeMapAction = (x) => Test(x);
item.AddAfterMapAction(beforeMapAction);
}
var dest = mapper.Map<Student, StudentVM>(StudentService.GetStudent());
Console.ReadLine();
}
catch (Exception ex)
{
}
}
public static void Test(int x)
{
Console.WriteLine("X = {0}", x);
}
It is not invoking the Test method when I am mapping using this line: var dest = mapper.Map<Student, StudentVM>(StudentService.GetStudent());
Am I doing anything wrong here. As it should call the Test method while mapping.
You can't modify maps after MappingConfiguration is instantiated. Once a TypeMap is built, the execution plan is created and can't change.
You need to move that AfterMap configuration into where you're configuring.
I'm trying to make live notification using signalR. My project is running on localhost. But I don't see my notification when I set webconfig server-side. (although I did it with signalR)
When I run the 'internet' part of Chrome 's check item, I see that the request does not fall. how do I make this problem?
ajax code;
function updateNotification() {
$('#notiContent').empty();
$('#notiContent').append($('<li>Yükleniyor...</li>'));
$.ajax({
type: 'GET',
datatype : JSON,
contentType: 'application/json; charset=utf-8',
url: '/notification/GetNotificationFlows',
success: function (response) {
$('#notiContent').empty();
if (response.length == 0) {
$('#notiContent').append($('<li>Data yok..</li>'));
}
$.each(response, function (index, value) {
$('#notiContent').append($('<li>Yeni kişi : ' + value.flowName + ' (' + value.flowPhone + ') eklendi.</li>'));
});
},
error: function (error) {
console.log(error);
}
})
}
Global.asax;
string con = ConfigurationManager.ConnectionStrings["sqlConString"].ConnectionString;
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
SqlDependency.Start(con);
}
protected void Session_Start(object sender, EventArgs e)
{
NotificationComponent NC = new NotificationComponent();
var currentTime = DateTime.Now;
HttpContext.Current.Session["LastUpdated"] = currentTime;
NC.RegisterNotification(currentTime);
}
protected void Application_End()
{
//here we will stop Sql Dependency
SqlDependency.Stop(con);
}
}
Notification component
public void RegisterNotification(DateTime currentTime)
{
string conStr = ConfigurationManager.ConnectionStrings["sqlConString"].ConnectionString;
string sqlCommand = #"SELECT [flowId],[flowName],[flowEMail],[flowPhone],[kaynakId] from [dbo].[flow] where [createDate] > #createDate";
//you can notice here I have added table name like this [dbo].[Contacts] with [dbo], its mendatory when you use Sql Dependency
using (SqlConnection con = new SqlConnection(conStr))
{
SqlCommand cmd = new SqlCommand(sqlCommand, con);
cmd.Parameters.AddWithValue("#createDate", currentTime);
if (con.State != System.Data.ConnectionState.Open)
{
con.Open();
}
cmd.Notification = null;
SqlDependency sqlDep = new SqlDependency(cmd);
sqlDep.OnChange += sqlDep_OnChange;
//we must have to execute the command here
using (SqlDataReader reader = cmd.ExecuteReader())
{
// nothing need to add here now
}
}
}
void sqlDep_OnChange(object sender, SqlNotificationEventArgs e)
{
if (e.Type == SqlNotificationType.Change)
{
SqlDependency sqlDep = sender as SqlDependency;
sqlDep.OnChange -= sqlDep_OnChange;
//from here we will send notification message to client
var notificationHub = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
notificationHub.Clients.All.notify("eklendi.");
//re-register notification
RegisterNotification(DateTime.Now);
}
}
public List<flow> GetFlows(DateTime afterDate)
{
using (smartCMSEntities dc = new smartCMSEntities())
{
return dc.flow.Where(a => a.createDate > afterDate).OrderByDescending(a => a.createDate).ToList();
}
}
Notification Controller
public JsonResult GetNotificationFlows()
{
var notificationRegisterTime = Session["LastUpdated"] != null ? Convert.ToDateTime(Session["LastUpdated"]) : DateTime.Now;
NotificationComponent NC = new NotificationComponent();
var list = NC.GetFlows(notificationRegisterTime);
Session["LastUpdate"] = DateTime.Now;
return new JsonResult { Data = list, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
Notification Hub
public class NotificationHub : Hub
{
//public void Hello()
//{
// Clients.All.hello();
//}
}
SQL (for sql dependency)
ALTER DATABASE [db_name] SET ENABLE_BROKER with rollback immediate;
I had the same problem you need to create your function inside your Hub.
Let say
public class NotificationHub : Hub
{
public static void Send()
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
context.Clients.All.displayStatus();
}
}
And call it in your html
function updateNotificationCount() {
$('span.count').show();
var count = 0;
count = parseInt($('span.count').html()) || 0;
count++;
$('span.noti').css("color", "white");
// $('span.count').css({ "background-color": "red", "color": "white" });
$('span.count').html(count);
}
// signalr js code for start hub and send receive notification
var hub = $.connection.notificationHub;
// Declare a function on the hub hub so the server can invoke it
hub.client.displayStatus = function () {
updateNotificationCount();
};
// Start the connection
$.connection.hub.start();
I have added a custom authorization scheme like this...
public class AuthHandler : AuthenticationHandler<AuthOptions>
{
private readonly IUserIdentifierProvider userIdentifierProvider;
public AuthHandler(IUserIdentifierProvider userIdentifierProvider, IOptionsMonitor<AuthOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) :
base(options, logger, encoder, clock)
{
this.userIdentifierProvider = userIdentifierProvider;
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync() {
var ticket = ...
return Task.FromResult(AuthenticateResult.Success(ticket));
}
}
public static class AuthMiddlewareAppBuilderExtensions
{
public static AuthenticationBuilder AddCustomAuth(this AuthenticationBuilder builder, Action<AuthOptions> configureOptions)
{
return builder.AddScheme<AuthOptions, AuthHandler>("Custom Scheme", "Custom Auth", configureOptions);
}
}
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddMemoryCache();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "Custom Scheme";
options.DefaultChallengeScheme = "Custom Auth";
})
.AddCustomAuth(o => {});
services.AddDbContext<DomainDbContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
});
var serviceProvider = ConfigureUnity(services);
return serviceProvider;
}
When MVC creates the AuthHandler class upon a request, it doesn't use my dependency injection container returned from ConfigureServices.
I get the exception...
InvalidOperationException: Unable to resolve service for type
'Web.Auth.Abstract.IUserIdentifierProvider' while attempting to
activate 'AuthHandler'.
Why is it not using my container?
It works if I do...
services.AddTransient<IUserIdentifierProvider, UserIdentifierProvider>();
inside ConfigureServices. It doesn't appear to be looking in my container at all. So where on earth is it getting the instance from? It must be keeping a reference to the IServiceCollection passed to ConfigureServices and uses it instead of the one it's supposed to.
Looking with Reflector, the ConfigureServices method is called by the following function...
public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider, Type startupType, string environmentName)
{
ConfigureBuilder builder = FindConfigureDelegate(startupType, environmentName);
ConfigureServicesBuilder builder2 = FindConfigureServicesDelegate(startupType, environmentName);
ConfigureContainerBuilder configureContainerMethod = FindConfigureContainerDelegate(startupType, environmentName);
object instance = null;
if (!builder.MethodInfo.get_IsStatic() || ((builder2 != null) && !builder2.MethodInfo.get_IsStatic()))
{
instance = ActivatorUtilities.GetServiceOrCreateInstance(hostingServiceProvider, startupType);
}
Func<IServiceCollection, IServiceProvider> configureServicesCallback = builder2.Build(instance);
Action<object> configureContainerCallback = configureContainerMethod.Build(instance);
return new StartupMethods(instance, builder.Build(instance), delegate (IServiceCollection services) {
IServiceProvider provider = configureServicesCallback(services);
if (provider != null)
{
return provider;
}
if (configureContainerMethod.MethodInfo != null)
{
Type[] typeArray1 = new Type[] { configureContainerMethod.GetContainerType() };
Type serviceType = typeof(IServiceProviderFactory<>).MakeGenericType(typeArray1);
object requiredService = hostingServiceProvider.GetRequiredService(serviceType);
object[] objArray1 = new object[] { services };
object obj3 = serviceType.GetMethod("CreateBuilder").Invoke(requiredService, objArray1);
configureContainerCallback(obj3);
object[] objArray2 = new object[] { obj3 };
provider = (IServiceProvider) serviceType.GetMethod("CreateServiceProvider").Invoke(requiredService, objArray2);
}
else
{
provider = hostingServiceProvider.GetRequiredService<IServiceProviderFactory<IServiceCollection>>().CreateServiceProvider(services);
}
return provider ?? services.BuildServiceProvider();
});
}
If a provider is returned, it's done.
This makes no sense.
I hard coded the dependencies in the auth handler constructor.
Simple, eh?
I am struggling to get Swagger to document multi-tenanted routes in WebApi.I have used this approach before but never in a self-hosted project. It seems MultipleApiVersions is never invoked - when i've added logging code.
StatupConfig.cs
public class StartupConfig
{
private static ILog _logger = LogManager.GetLogger(nameof(StartupConfig));
public void Configure(IAppBuilder appBuilder)
{
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
config.Filters.Add(new ApiKeyAuthorizationFilter());
config.Filters.Add(new ApiInvocationMetricsFilter());
var assembly = Assembly.GetExecutingAssembly();
var builder = new ContainerBuilder();
builder.RegisterApiControllers(assembly);
builder.RegisterWebApiFilterProvider(config);
builder.RegisterAssemblyTypes(assembly).AsImplementedInterfaces();
var container = builder.Build();
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
appBuilder.UseAutofacMiddleware(container);
appBuilder.UseAutofacWebApi(config);
appBuilder.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
appBuilder.UseWebApi(config);
config
.EnableSwagger(c =>
{
c.MultipleApiVersions(
ResolveVersionSupportByRouteConstraint,
(vc) =>
{
vc.Version("v2", "API v2");
vc.Version("v1", "API v1");
});
c.RootUrl((message) => ConfigurationManager.AppSettings["SwaggerRoot"]);
c.IncludeXmlComments($#"{System.AppDomain.CurrentDomain.BaseDirectory}\API.xml");
c.OperationFilter<AddRequiredApiKeyParameter>();
c.DescribeAllEnumsAsStrings(true);
})
.EnableSwaggerUi(ui =>
{
ui.EnableDiscoveryUrlSelector();
});
}
public static bool ResolveVersionSupportByRouteConstraint(ApiDescription apiDesc, string targetApiVersion)
{
try
{
var versionConstraint = (apiDesc.Route.Constraints.ContainsKey("apiVersion"))
? apiDesc.Route.Constraints["apiVersion"] as ApiVersionConstraint
: null;
return versionConstraint?.AllowedVersion.Split('|').Select(x => x.ToLowerInvariant()).ToList().Contains(targetApiVersion.ToLowerInvariant()) ?? false;
}
catch (System.Exception excep)
{
_logger.Error("An error occurred resolving version support", excep);
throw;
}
}
}
Note: This predates WebApiVersioning so I am using a route constraint:
public class ApiVersion2RoutePrefixAttribute : RoutePrefixAttribute
{
private const string RouteBase = "api/{apiVersion:apiVersionConstraint(v2)}";
private const string PrefixRouteBase = "api/{apiVersion:apiVersionConstraint(v2)}/";
public ApiVersion2RoutePrefixAttribute(string routePrefix)
: base(string.IsNullOrWhiteSpace(routePrefix) ? "api/{apiVersion:apiVersionConstraint(v2)}" : "api/{apiVersion:apiVersionConstraint(v2)}/" + routePrefix)
{
}
}
Am I missing something here?
Thanks
KH
This problem was solved by ensuring any Name parameters in the Route attribute are unique across both controller versions. I had an operation named Add, with a Route Name parameter of Add across both controllers and this was preventing Swagger from functioning.
i.e this
[Route("", Name = nameof(AddAdvertiser))]
changed to this
[Route("", Name = nameof(V1AddAdvertiser))]