MassTransit handle Publish exception - .net-core

I'm using an IHostedService in order to Publish a list of events to RabbitMQ, using MassTransit.
I now wanted to handle exceptions on Publish, when for example RabbitMQ is not available.
Then my idea is to mark the db row relative to the message to be sent with Error = 1, TransientError = 1, so that next time it will be tried to be sent again.
Here is how I configure MassTransit
services.AddMassTransit(x =>
{
x.AddBus(provider => Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.Host(new Uri(_configuration["RabbitMQ:URI"] + _configuration["RabbitMQ:VirtualHost"]), $"ENG {_configuration["Application:PlantID"]} Producer", h =>
{
h.Username(_configuration["RabbitMQ:UserName"]);
h.Password(_configuration["RabbitMQ:Password"]);
});
cfg.Publish<UpdateNorm>(x =>
{
x.Durable = true;
x.AutoDelete = false;
x.ExchangeType = "fanout"; // default, allows any valid exchange type
});
cfg.ConfigurePublish(x => x.UseExecute(x =>
{
x.Headers.Set("SiteID", _configuration["Application:PlantID"]);
}));
}));
});
//OPTIONAL, but can be used to configure the bus options
services.AddOptions<MassTransitHostOptions>()
.Configure(options =>
{
// if specified, waits until the bus is started before
// returning from IHostedService.StartAsync
// default is false
options.WaitUntilStarted = false;
// if specified, limits the wait time when starting the bus
//options.StartTimeout = TimeSpan.FromSeconds(10);
// if specified, limits the wait time when stopping the bus
options.StopTimeout = TimeSpan.FromSeconds(30);
});
And here my IHostedService implementation
public class MessageBrokerQueueBackgroundService : BackgroundService
{
private readonly ILogger<MessageBrokerQueueBackgroundService> logger;
private readonly IPublishEndpoint publishEndpoint;
private readonly int MessageBrokerQueueCheckMillis;
private readonly DB db;
private readonly BLMessageBrokerQueue blMessageBrokerQueue;
public MessageBrokerQueueBackgroundService(
DB db,
BLMessageBrokerQueue blMessageBrokerQueue,
IPublishEndpoint publishEndpoint,
ILogger<MessageBrokerQueueBackgroundService> logger,
IConfiguration configuration)
{
this.db = db;
this.blMessageBrokerQueue = blMessageBrokerQueue;
this.publishEndpoint = publishEndpoint;
this.logger = logger;
this.MessageBrokerQueueCheckMillis = Convert.ToInt32(configuration["MessageBrokerQueueCheckMillis"]);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
logger.LogDebug($"MessageBrokerQueueBackgroundService is starting.");
stoppingToken.Register(() =>
{
End(stoppingToken);
});
while (!stoppingToken.IsCancellationRequested)
{
ICollection<MessageBrokerQueue> messageBrokerqueueList;
try
{
messageBrokerqueueList = await blMessageBrokerQueue.GetMessageBrokerQueueListAsync();
foreach (var element in messageBrokerqueueList.OrderBy(x => x.PK))
{
try
{
if (element.Context == "Norm")
{
await publishEndpoint.Publish<UpdateNorm>(new
{
element.Key1,
}, stoppingToken);
}
// define other contexts
else
{
throw new MessageBrokerQueueUnknownContextException($"Unknown Context: {element.Context}", element.Context);
}
await blMessageBrokerQueue.MessageSentAsync(element);
logger.LogInformation("MessageBrokerQueueBackgroundService Message Context: {Context}, Key1: {Key1}, Key2: {Key2}, Key3: {Key3} correctly Published.", element.Context, element.Key1, element.Key2, element.Key3);
}
catch (MessageBrokerQueueUnknownContextException e)
{
logger.LogError(e, "MessageBrokerQueueBackgroundService unknown Context: {Context}.", e.Context);
await blMessageBrokerQueue.MessageNonTransientErrorAsync(element, $"Unknown Context {e.Context}");
}
//catch (Exception Rabbit not available e)
//{
// logger.LogError(e, "MessageBrokerQueueBackgroundService Generic Exception threaded as transient");
// await blMessageBrokerQueue.MessageTransientErrorAsync(element, e.Message);
//}
catch (Exception e)
{
logger.LogError(e, "MessageBrokerQueueBackgroundService Generic Exception threaded as NOT transient");
await blMessageBrokerQueue.MessageNonTransientErrorAsync(element, e.Message);
}
}
}
catch (Exception e)
{
logger.LogError(e, $"MessageBrokerQueueBackgroundService error while processing queue.");
}
finally
{
await Task.Delay(MessageBrokerQueueCheckMillis, stoppingToken);
}
}
}
protected Task End(CancellationToken stoppingToken)
{
logger.LogDebug($"MessageBrokerQueueBackgroundService background task is stopping.");
return Task.CompletedTask;
}
}
I tried to Publish with Rabbit shut down, but this method hang forever
await publishEndpoint.Publish<UpdateNorm>(new
{
element.Key1,
}, stoppingToken);
until I restart Rabbit, then it continues and finishes correctly.
I want to avoid to wait indefinitely. My idea was to wait for some seconds and then mark for this exception the error as transient.

Related

Headers are read-only, response has already started

I am trying to catch and format the exception thrown by the resource filter but getting this error. The middleware is working for exceptions thrown from controller level but getting this - "System.InvalidOperationException: Headers are read-only, response has already started" error while trying to write to the response in case of resource level errors.
Code of my Resource Filter:
public class TestingAsyncResourceFilter : IAsyncResourceFilter
{
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
{
Console.WriteLine("Resource filter executing");
var resourceExecutedContext = await next();
Console.WriteLine("Resource filter executed");
if (!resourceExecutedContext.ModelState.IsValid)
{
throw new CustomUPException();
}
}
}
Code of middleware:
public class ResponseFormatterMiddleware : IMiddleware
{
private readonly ILogger<ResponseFormatterMiddleware> _logger;
public ResponseFormatterMiddleware(ILogger<ResponseFormatterMiddleware> logger)
{
_logger = logger;
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
try
{
Console.WriteLine("Before execution");
await next(context);
Console.WriteLine("After Execution");
}
catch(CustomUPException e)
{
Console.WriteLine("Here we are");
await context.Response.WriteAsJsonAsync(
new ResponseDto()
{
statusCode = e.statusCode,
message = e.message
}); // getting error
}
catch(Exception e)
{
_logger.LogError(e.Message);
context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
await context.Response.WriteAsJsonAsync(
new ResponseDto()
{
success = false,
message = "Request failed"
});
}
}
}
Code of my controller:
[Route("api/[controller]")]
[ApiController]
public class TestingController : ControllerBase
{
[HttpPost("/resource")]
public async Task<UserDto> testingResource( [FromBody] UserDto dto)
{
if (dto.email.Contains("hell"))
{
throw new CustomUPException(); //working
}
return dto;
}
}
Instead of using resource filter, I have used this strategy for formatting model validation errors as the documentation suggests.
Curious to know more about the raised issue though. Thanks in advance
// Add services to the container.
builder.Services.AddControllers().ConfigureApiBehaviorOptions(
options =>
{
options.InvalidModelStateResponseFactory = context =>
{
if (!context.ModelState.IsValid)
{
var data = new Dictionary<string, string?>();
//My Response formatter
var modelStateDictionary = context.ModelState;
foreach (var key in modelStateDictionary.Keys)
{
var errors = modelStateDictionary[key]?.Errors;
data.TryAdd(key, errors?[0].ErrorMessage);
}
return new ObjectResult(new UniversalResponseDto()
{
data = data,
statusCode = (int)HttpStatusCode.UnprocessableEntity,
sucess = false,
message = "One or more validation error occured"
})
{
StatusCode = (int)HttpStatusCode.UnprocessableEntity,
};
}
return new ObjectResult(context.HttpContext.Response);
};
});

Confluent Kafka consumer consumes messages only after changing groupId

I have a .Net core console application, that uses Confluent.Kafka.
I build a consumer for consuming messages from specific topic.
the app is intended to run a few times every-day, consume the messages on the specified topic and process them.
It took me a while to understand the consumer's vehavior, but the it will consume messages only if its groupId is a one that was never in use before.
Every time I change the consumer's groupId - the comsumer will fetch the messages in the subscribed topic. But on the next runs - with same groupId - the consumer.Consume returns null.
This behvior seems rlated to rebalance between consumers on same group. But I don't understand why - since the consumer should exist only throughout the application liftime. Before leaving the app, I call to consumer.close() and consumer.Dispose(). These should destoy the consumer, so that on the next run, when I create the consumer, again it will be the first and single consumer on the specified groupId. But as I said, this is not what happens in fact.
I know there are messages on the topic - I check it via command-line. And I also made sure the topic has only 1 partition.
The most weird thing is, that I have another .net core console app, which does the same process - and with no issue at all.
I attach the codes of the 2 apps.
Working app - always consuming:
class Program
{
...
static void Main(string[] args)
{
if (args.Length != 2)
{
Console.WriteLine("Please provide topic name to read and SMTP topic name");
}
else
{
var services = new ServiceCollection();
services.AddSingleton<ConsumerConfig, ConsumerConfig>();
services.AddSingleton<ProducerConfig, ProducerConfig>();
var serviceProvider = services.BuildServiceProvider();
var cConfig = serviceProvider.GetService<ConsumerConfig>();
var pConfig = serviceProvider.GetService<ProducerConfig>();
cConfig.BootstrapServers = Environment.GetEnvironmentVariable("consumer_bootstrap_servers");
cConfig.GroupId = "confluence-consumer";
cConfig.EnableAutoCommit = true;
cConfig.StatisticsIntervalMs = 5000;
cConfig.SessionTimeoutMs = 6000;
cConfig.AutoOffsetReset = AutoOffsetReset.Earliest;
cConfig.EnablePartitionEof = true;
pConfig.BootstrapServers = Environment.GetEnvironmentVariable("producer_bootstrap_servers");
var consumer = new ConsumerHelper(cConfig, args[0]);
messages = new Dictionary<string, Dictionary<string, UserMsg>>();
var result = consumer.ReadMessage();
while (result != null && !result.IsPartitionEOF)
{
Console.WriteLine($"Current consumed msg-json: {result.Message.Value}");
...
result = consumer.ReadMessage();
}
consumer.Close();
Console.WriteLine($"Done consuming messages from topic {args[0]}");
}
}
class ConsumerHelper.cs
namespace AggregateMailing
{
using System;
using Confluent.Kafka;
public class ConsumerHelper
{
private string _topicName;
private ConsumerConfig _consumerConfig;
private IConsumer<string, string> _consumer;
public ConsumerHelper(ConsumerConfig consumerConfig, string topicName)
{
try
{
_topicName = topicName;
_consumerConfig = consumerConfig;
var builder = new ConsumerBuilder<string, string>(_consumerConfig);
_consumer = builder.Build();
_consumer.Subscribe(_topicName);
}
catch (System.Exception exc)
{
Console.WriteLine($"Error on ConsumerHelper: {exc.ToString()}");
}
}
public ConsumeResult<string, string> ReadMessage()
{
Console.WriteLine("ReadMessage: start");
try
{
return _consumer.Consume();
}
catch (System.Exception exc)
{
Console.WriteLine($"Error on ReadMessage: {exc.ToString()}");
return null;
}
}
public void Close()
{
Console.WriteLine("Close: start");
try
{
_consumer.Close();
_consumer.Dispose();
}
catch (System.Exception exc)
{
Console.WriteLine($"Error on Close: {exc.ToString()}");
}
}
}
}
Not working app - consuming only on first run after changing consumer groupId to one never in use:
class Program.cs
class Program
{
private static SmtpClient smtpClient;
private static Random random = new Random();
static void Main(string[] args)
{
try
{
var services = new ServiceCollection();
services.AddSingleton<ConsumerConfig, ConsumerConfig>();
services.AddSingleton<SmtpClient>(new SmtpClient("smtp.gmail.com"));
var serviceProvider = services.BuildServiceProvider();
var cConfig = serviceProvider.GetService<ConsumerConfig>();
cConfig.BootstrapServers = Environment.GetEnvironmentVariable("consumer_bootstrap_servers");
cConfig.GroupId = "smtp-consumer";
cConfig.EnableAutoCommit = true;
cConfig.StatisticsIntervalMs = 5000;
cConfig.SessionTimeoutMs = 6000;
cConfig.AutoOffsetReset = AutoOffsetReset.Earliest;
cConfig.EnablePartitionEof = true;
var consumer = new ConsumerHelper(cConfig, args[0]);
...
var result = consumer.ReadMessage();
while (result != null && !result.IsPartitionEOF)
{
Console.WriteLine($"current consumed message: {result.Message.Value}");
var msg = JsonConvert.DeserializeObject<EmailMsg>(result.Message.Value);
result = consumer.ReadMessage();
}
Console.WriteLine("Done sending emails consumed from SMTP topic");
consumer.Close();
}
catch (System.Exception exc)
{
Console.WriteLine($"Error on Main: {exc.ToString()}");
}
}
class ConsumerHelper.cs
using Confluent.Kafka;
using System;
using System.Collections.Generic;
namespace Mailer
{
public class ConsumerHelper
{
private string _topicName;
private ConsumerConfig _consumerConfig;
private IConsumer<string, string> _consumer;
public ConsumerHelper(ConsumerConfig consumerConfig, string topicName)
{
try
{
_topicName = topicName;
_consumerConfig = consumerConfig;
var builder = new ConsumerBuilder<string, string> (_consumerConfig);
_consumer = builder.Build();
_consumer.Subscribe(_topicName);
//_consumer.Assign(new TopicPartition(_topicName, 0));
}
catch (System.Exception exc)
{
Console.WriteLine($"Error on ConsumerHelper: {exc.ToString()}");
}
}
public ConsumeResult<string, string> ReadMessage()
{
Console.WriteLine("ConsumeResult: start");
try
{
return _consumer.Consume();
}
catch (System.Exception exc)
{
Console.WriteLine($"Error on ConsumeResult: {exc.ToString()}");
return null;
}
}
public void Close()
{
Console.WriteLine("Close: start");
try
{
_consumer.Close();
_consumer.Dispose();
}
catch (System.Exception exc)
{
Console.WriteLine($"Error on Close: {exc.ToString()}");
}
Console.WriteLine("Close: end");
}
}
}

SignalR - sending push notification to a specific user

I'm working on a PoC for a notification engine. I'm able to successfully connect and call Hub functions from JS, but I can't seem to get push notifications to work. I'm getting an Object reference not set to an instance of an object error.
Triggering class
// I was able to confirm that the connectionIds are valid
public void HandleEvent(NewNotificationEvent eventMessage)
{
// _connections handles connectionids of a user
// multiple connection ids to handle multiple open tabs
var connectionIds = _connections.GetConnectionsByUser(eventMessage.Notification.UserId);
foreach(var connectionId in connectionIds)
{
// a client is returned, but aside from the connectionid, all the properties are either null or empty
var client = _notificationHub.Clients.Client(connectionId);
///// ERROR HAPPENS HERE
///// I'm guessing NewNotification() isn't defined somewhere, but I don't know where.
client.NewNotification("Hello");
}
}
View.cshtml
var notificationHub = $.connection.NotificationHub;
$.connection.hub.qs="userId=#userId"
// Initialization
$.connection.hub.start().done(function () {
// get count unread notification count
notificationHub.invoke("unReadNotificationsCount")
.done((unreadCount) => {
if (unreadCount > 0) {
$('#notificationBadge').html(unreadCount);
hasNewNotification = true;
}
console.log('SignalR connected!');
})
.fail((data) => {
console.log('Unable to reach server');
console.log(data);
})
.always(() => $('#dropdownNotificationOptions').show());
});
// also tried notificationHub.NewNotification()
notificationHub.client.NewNotification = function (notification) {
console.log(notification);
}
NotificationHub.cs
[HubName("NotificationHub")]
public class NotificationHub : Hub
{
//ctor
public override Task OnConnected()
{
var userId = Context.QueryString["userid"];
if(userId.IsNotNullOrEmpty())
_connections.Add(Context.ConnectionId, Guid.Parse(userId));
else
_connections.Add(Context.ConnectionId, Guid.NewGuid());
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled = true)
{
_connections.Remove(Context.ConnectionId);
return base.OnDisconnected(stopCalled);
}
public override Task OnReconnected()
{
Guid userId;
if (Guid.TryParse(Context.QueryString["userid"],out userId))
{
//var userId = _workContext.CurrentUser.Id;
var userConnection = _connections.GetUserByConnection(Context.ConnectionId);
if (userConnection == null || userConnection.IsNotNullOrEmpty())
{
_connections.Add(Context.ConnectionId, userId);
}
}
return base.OnReconnected();
}
}
You should have your NewNotification before the $.connection.hub.start() such as:
var notificationHub = $.connection.NotificationHub;
$.connection.hub.qs="userId=#userId"
// Moved to define before the connection start
notificationHub.client.NewNotification = function (notification) {
console.log(notification);
}
// Initialization
$.connection.hub.start().done(function () {
// get count unread notification count
notificationHub.invoke("unReadNotificationsCount")
.done((unreadCount) => {
if (unreadCount > 0) {
$('#notificationBadge').html(unreadCount);
hasNewNotification = true;
}
console.log('SignalR connected!');
})
.fail((data) => {
console.log('Unable to reach server');
console.log(data);
})
.always(() => $('#dropdownNotificationOptions').show());
});

Calling Async task in button click in xamarin.forms

I have xamarin.forms app contains a listview which will load values from Rest API.Which is working fine.I have button just above the listview.When I click on the button, the listview API call will be placed again and the listview should update. But stuck at this update part.I am not using MVVM pattern.The listview listing portion is an async Task.I am calling the async task again when the button click, but App gets crash. Is it due to calling the async task again from button click? Any help is appreciated.
Here is My code.
namespace app
{
public partial class List : ContentPage
{
PendingWeekRange pendingWeekRange = new PendingWeekRange();
public TimeSheetList()
{
InitializeComponent();
Task.Run(async () =>
{
await LoadScreenItems();
});
}
async Task LoadScreenItems()
{
await Task.Run(async () => {
try
{
// Doing some stuff
await loadTimeSheetList();
}
catch (Exception)
{
}
});
}
async Task loadTimeSheetList()
{
await Task.Run(() => { + string postdataForPendingList = "{\"date\":\"" + "1" + "\"}";
APICall callForAPICallResult = new APICall("/API/ListMobile/ListForApproval", postdataForList, loadingIndicator);
try
{
List<ListData> resultObjForPendingTimeSheetList = callForAPICallResult<List<ListData>>();
if (resultObjForPendingTimeSheetList != null)
{
TimesheetList.ItemsSource = resultObjForPendingTimeSheetList;
screenStackLayout.VerticalOptions = LayoutOptions.FillAndExpand;
TimesheetList.IsVisible = true;
}
else
{
}
}
catch (Exception)
{
}
});
}
async void Button_Tapped(object sender, EventArgs e)
{
try
{
// Calling my listview again. After calling app gets crash
Task.Run(async () => await loadTimeSheetList());
}
catch (Exception ex) { }
}
}
}
A few things before getting to the problem. You've got async/await all wrong, go though Async Programming
Task.Run runs the passed action on a different thread, if you make changes to UI elements on this thread, your app will definitely(take my word) crash.
If you want to make async call at page launch, make use of OnAppearing method (if you only want to call once, maintain a flag)
Do not change the ItemsSource of a list view frequently, just clear and add items to it.
namespace app
{
public partial class List : ContentPage
{
PendingWeekRange pendingWeekRange = new PendingWeekRange();
private ObservableCollection<ListData> TimesheetObservableCollection = new ObservableCollection<ListData>();
public TimeSheetList()
{
InitializeComponent();
TimesheetList.ItemsSource = TimesheetObservableCollection;
}
protected override async OnAppearing()
{
// flag for first launch?
await LoadScreenItems();
}
async Task LoadScreenItems()
{
try
{
// Doing some stuff
TimesheetObservableCollection.Clear();
TimesheetObservableCollection.AddRange(await GetTimeSheetList());
}
catch (Exception)
{
//handle exception
}
}
async Task<List<ListData>> GetTimeSheetList()
{
string postdataForPendingList = "{\"date\":\"" + "1" + "\"}";
APICall callForAPICallResult = new APICall("/API/ListMobile/ListForApproval", postdataForList, loadingIndicator);
try
{
return callForAPICallResult<List<ListData>>();
}
catch (Exception)
{
// handle exception
}
}
async void Button_Tapped(object sender, EventArgs e)
{
try
{
// Calling my listview again. After calling app gets crash
TimesheetObservableCollection.Clear();
TimesheetObservableCollection.AddRange(await GetTimeSheetList());
}
catch (Exception ex) { }
}
}
}
#Androdevil,
Update your loadTimeSheetList with this,
async Task loadTimeSheetList()
{
try
{
// I am calling my API for Listview here.
List<TimeSheetListData> resultObjForPendingTimeSheetList = await callForPendingTimeSheetList.APICallResult<List<TimeSheetListData>>();
if (resultObjForPendingTimeSheetList != null)
{
TimesheetList.ItemsSource = resultObjForPendingTimeSheetList;
screenStackLayout.VerticalOptions = LayoutOptions.FillAndExpand;
TimesheetList.IsVisible = true;
}
else
{
}
}
catch (Exception)
{
}
}

Why cant change textbox text in client function SignalR

I just start testing signalr and I am trying to add text to a rich text box after I got a response from my HUB class , but it doesn't work (no text is shown in my richtextbox) I don't know why...(the code run with no errors)
//server
public class ConnectByHub : Hub
{
public void testFunc(mas) {
string ans = mas + " got it";
Clients.All.testFunc(ans);
} }
//Client
private async void connectToServer()
{
Connection = new HubConnection(LocalClient);
HubProxy = Connection.CreateHubProxy("ConnectByHub");
try
{
await Connection.Start();
}
catch (Exception ex)
{
return;
}
string msg = "Hello friend!";
HubProxy.Invoke("testFunc", (msg)).Wait();
// Option one - doesn't work
HubProxy.On<string>("testFunc", (param) => Invoke((Action)(() => { MsgTxtBox.Text = "something happened"; })));
//Option two - doesn't work
HubProxy.On<string>("testFunc", (param) => this.Invoke((Action)(() => { MsgTxtBox.AppendText("Something happend " + Environment.NewLine); })));
}
I think part of the problem is trying to send a message from the same Async method (connectToServer) in which your listener is running.
I mostly used the same code from the question but moved a couple things around:
Moved HubProxy.Invoke() out of the Async method and called it from a button_click event
Called string.format() on the parameter
SERVER:
public class ConnectByHub : Hub
{
public void Send(string message)
{
Clients.All.testFunc(message);
}
}
CLIENT:
// Added button event
private void button1_Click(object sender, EventArgs e)
{
string msg = "Hello friend!";
HubProxy.Invoke("Send", msg).Wait();
}
private async void ConnectToServerAsync()
{
Connection = new HubConnection(LocalClient);
HubProxy = Connection.CreateHubProxy("ConnectByHub");
// Put the parmater in string.format()
HubProxy.On<string>("testFunc", (param) => this.Invoke((Action)(() => MsgTxtBox.AppendText(string.Format("{0}", param)))));
try
{
await Connection.Start();
}
catch (Exception ex)
{
richTextBox1.AppendText(string.Format("Unable to Connect to server ({0})", ServerURI));
return;
}
}

Resources