SSE Eventsource Asp.net Core 3.1 - asp.net-core

[ApiController]
[Route("/SSE/[action]")]
public class SSEController : Controller
{
private static ConcurrentBag<StreamWriter> clients;
static SSEController()
{
clients = new ConcurrentBag<StreamWriter>();
}
[HttpPost]
public async Task SSECallbackMsg()
{
await CallbackMsg("test");
}
private async Task CallbackMsg(string test)
{
foreach (var client in clients)
{
try
{
var data = string.Format(test);
await client.WriteAsync(data);
await client.FlushAsync();
client.Dispose();
}
catch (Exception)
{
StreamWriter ignore;
clients.TryTake(out ignore);
}
}
}
[HttpGet]
public HttpResponseMessage GETSubscibe()
{
HttpResponseMessage response = new HttpResponseMessage();
response.Content = new PushStreamContent((a, b, c) =>
{ OnStreamAvailable(a, b, c); }, "text/event-stream");
return response;
}
private void OnStreamAvailable(Stream stream, HttpContent content,
TransportContext context)
{
var client = new StreamWriter(stream,Encoding.UTF8);
clients.Add(client);
}
}
Javascript Method of calling above is like
function listenForServerEvents() {
var source = new EventSource('https://localhost:5002/SSE/GETSubscibe');
source.addEventListener("open", function (event) {
console.log('onopen');
}, false);
source.addEventListener("error", function (event) {
if (event.eventPhase == EventSource.CLOSED) {
source.close();
}
}, false);
source.addEventListener("message", function (event) {
console.log('onmessage: ' + event.data);
}, false);
}
when executing, above js function, i am getting error as EventSource's response has a MIME type ("application/json") that is not "text/event-stream". Aborting the connection.
Should add anything in startup.cs or is there any mistake., If anyone knows ,kindly help

Related

Asp.net Core 3.1 Web Api return custom error message from IActionResult

Is it possible to return custom error messages to client from Asp.Net Core 3.1 Web Api? I've tried a few different things to set the "ReasonPhrase" with no luck. I have tried using StatusCode:
return StatusCode(406, "Employee already exists");
I tried to return using HttpResponseMessage:
HttpResponseMessage msg = new HttpResponseMessage();
msg.StatusCode = HttpStatusCode.NotAcceptable;
msg.ReasonPhrase = "Employee alredy exists";
return (IActionResult)msg;
I am trying to return a message to the client calling the method that the employee already exists:
public async Task<IActionResult> CreateEmployee([FromBody] EmployeeImport Employee)
{
var exists = await employeeService.CheckForExistingEmployee(Employee);
if (exists > 0)
{
//return StatusCode(406, "Employee already exists");
HttpResponseMessage msg = new HttpResponseMessage();
msg.StatusCode = HttpStatusCode.NotAcceptable;
msg.ReasonPhrase = "Employee already exists";
return (IActionResult)msg;
}
}
This is the code in the client:
public async Task<ActionResult>AddEmployee(EmployeeImport employee)
{
var message = await CommonClient.AddEmployee(employee);
return Json(message.ReasonPhrase, JsonRequestBehavior.AllowGet);
}
public async Task<HttpResponseMessage> AddEmployee(EmployeeImport employee)
{
var param = Newtonsoft.Json.JsonConvert.SerializeObject(employee);
HttpContent contentPost = new StringContent(param, System.Text.Encoding.UTF8, "application/json");
var response = await PerformPostAsync("entity/NewEmployee", contentPost);
return response;
}
protected async Task<HttpResponseMessage> PerformPostAsync(string requestUri, HttpContent c)
{
_webApiClient = new HttpClient { BaseAddress = _baseAddress };
_webApiClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
var webApiResponse = await _webApiClient.PostAsync(requestUri, c);
return webApiResponse;
}
To do this, you can create a Custom Error class that implements the IActionResult interface as follows:
public class CustomError : IActionResult
{
private readonly HttpStatusCode _status;
private readonly string _errorMessage;
public CustomError(HttpStatusCode status, string errorMessage)
{
_status = status;
_errorMessage = errorMessage;
}
public async Task ExecuteResultAsync(ActionContext context)
{
var objectResult = new ObjectResult(new
{
errorMessage = _errorMessage
})
{
StatusCode = (int)_status,
};
context.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = _errorMessage;
await objectResult.ExecuteResultAsync(context);
}
}
And use the following form :
[HttpGet]
public IActionResult GetEmployee()
{
return new CustomError(HttpStatusCode.NotFound, "The employee was not found");
}
Try changing
return (IActionResult)msg;
to
return Task.FromResult(BadRequest(msg) as IActionResult);

How to get response via Masstransit?

I want to get a response (GetStatusResponse) from consumer (GetStatusConsumer).
My request is putted in Rabbit queue "getstatus" but my consumer is not rise and timeout occurs.
Publish-method and Consumer nested in one project
It seems to me trouble in the Startup.cs. Could you help me?
I have following code in Startup.cs
...
services.AddSingleton(provider =>
{
var getStatusBusOptions = provider.GetRequiredService<IOptions<BusOptions>>().Value;
var bus = Bus.Factory.CreateUsingRabbitMq(sbc =>
{
var host = sbc.Host(new Uri(getStatusBusOptions.HostUri), h =>
{
h.Username(getStatusBusOptions.UserName);
h.Password(getStatusBusOptions.Password);
});
sbc.ReceiveEndpoint("getstatus", ep =>
{
ep.Consumer<GetStatusConsumer>(provider);
ep.PrefetchCount = getStatusBusOptions.PrefetchCount;
ep.UseConcurrencyLimit(getStatusBusOptions.UseConcurrencyLimit);
});
});
return new GetStatusBus
{
Bus = bus,
HostUri = getStatusBusOptions.HostUri
};
});
...
Following code in class GetStatusPublisher.cs
public class GetStatusPublisher : IGetStatusPublisher
{
readonly GetStatusBus _bus;
public GetStatusPublisher(GetStatusBus bus)
{
_bus = bus;
}
public async Task<Tout> GetResponse<Tin, Tout>(Tin request) where Tin : class where Tout : class
{
var serviceAddress = new Uri($"rabbitmq://rabbitmq.test.com/jgt/getstatus");
var timeout = TimeSpan.FromSeconds(30);
var client = new MessageRequestClient<Tin, Tout>(_bus.Bus, serviceAddress, timeout);
var resp = await client.Request(request); // <== Timeout here and don't rise consumer (GetStatusConsumer)
return resp;
}
Here is Publish-method:
...
readonly IGetStatusPublisher _getStatusPublisher;
...
var resp = await _getStatusPublisher.GetResponse<GetStatusRequest, GetStatusResponse>(statusReq);
Consumer has following code:
public class GetStatusConsumer : MetricWriter, IConsumer<GetStatusRequest>
{
public GetStatusConsumer(IMetrics metrics) : base(metrics)
{
......
}
public async Task Consume(ConsumeContext<GetStatusRequest> context)
{
....
}
}
First things first, I don't think you're starting the bus. That's the major issue.
However...
Are you using an antique version of MassTransit? Your code is seriously out of date, and I'd suggest updating it to use the current version/syntax as your code example above has so many things wrong with it.
services.AddMassTransit(x =>
{
x.AddConsumer<GetStatusConsumer>();
x.UsingRabbitMq((context, cfg) =>
{
var getStatusBusOptions = context.GetRequiredService<IOptions<BusOptions>>().Value;
cfg.Host(new Uri(getStatusBusOptions.HostUri), h =>
{
h.Username(getStatusBusOptions.UserName);
h.Password(getStatusBusOptions.Password);
});
cfg.PrefetchCount = getStatusBusOptions.PrefetchCount;
cfg.ConcurrentMessageLimit = getStatusBusOptions.UseConcurrencyLimit;
cfg.ConfigureEndpoints(context);
});
});
services.AddMassTransitHostedService();
services.AddGenericRequestClient();
Then, you can simply add a dependency on IRequestClient<T> to send requests and get responses. Your updated publisher code may look like:
public class GetStatusPublisher :
IGetStatusPublisher
{
public GetStatusPublisher(IServiceProvider provider)
{
_provider = provider;
}
public async Task<Tout> GetResponse<Tin, Tout>(Tin request) where Tin : class where Tout : class
{
var client = _provider.GetRequiredService<IRequestClient<Tin>>();
var response = await client.GetResponse<Tout>(request);
return response.Message;
}
}
If I use following code I get error: "System.InvalidOperationException: Cannot resolve scoped service 'MassTransit.IRequestClient`1[GetStatusTry.Contracts.GetStatusRequest]' from root provider....."
public async Task<Tout> GetResponse<Tin, Tout>(Tin request) where Tin : class where Tout : class
{
try
{
var client = _provider.GetRequiredService<IRequestClient<Tin>>(); // << --- here is error
var response = await client.GetResponse<Tout>(request);
return response.Message;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw;
}
}
But this code works Ok:
public class Dev2Controller : ControllerBase
{
IRequestClient<GetStatusRequest> _client;
public Dev2Controller(IGetStatusPublisher getStatusPublisher, IRequestClient<GetStatusRequest> client)
{
_client = client;
}
[HttpPost]
public async Task<GetStatusResponse> GetStatus2()
{
var req = new GetStatusRequest { Statuses = new List<int> { 1, 2, 3 }, TerminalDescr = "try to get status2" };
var response = await _client.GetResponse<GetStatusResponse>(req);
return (response.Message);
}
}
this code is working
public class GetStatusPublisher : IGetStatusPublisher
{
readonly IServiceProvider _provider;
public GetStatusPublisher(IServiceProvider provider)
{
_provider = provider;
}
public async Task<Tout> GetResponse<Tin, Tout>(Tin request) where Tin : class where Tout : class
{
try
{
using (var _scope = _provider.CreateScope())
{
var client = _scope.ServiceProvider.GetRequiredService<IRequestClient<Tin>>();
var response = await client.GetResponse<Tout>(request);
return response.Message;
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw;
}
}
}

Receiving empty response on a PostAsJsonAsync

I have this API action:
[HttpPost("MergeProjects")]
public async Task<ActionResult<(bool Result, string Message)>> MergeProjects(IEnumerable<DesignProjectViewModel> projects)
{
var mergeResult = await _projectService.MergeProjects(projects);
return Ok(mergeResult);
}
Here is the code in action returning a message with Ok:
On client side, I have this code:
public async Task<(bool Result, string Message)> MergeProjects(IEnumerable<DesignProjectViewModel> selectedProjects)
{
var response = await ConnectingClient.PostAsJsonAsync("api/designProject/mergeProjects", selectedProjects);
if (!response.IsSuccessStatusCode) return (false, "Failed");
var result = await response.Content.ReadAsStringAsync();
var result1 = await response.Content.ReadFromJsonAsync<(bool, string)>();
return JsonConvert.DeserializeObject<(bool Result, string Message)>(result);
}
I'm experimenting.
I'm not able to find anything in the response.Contents to parse:
What could be the issue. The API returns a json response.
You can try to put the data into an object,and return an object:
Api:
[HttpPost("MergeProjects")]
public async TestObject MergeProjects(IEnumerable<DesignProjectViewModel> projects)
{
var mergeResult = await _projectService.MergeProjects(projects);
return new TestObject { MyProperty1= mergeResult .Item1, MyProperty2=mergeResult .Item2};
}
Model:
public class TestObject
{
public bool MyProperty1 { get; set; }
public string MyProperty2 { get; set; }
}
Controller:
public async Task<TestObject> MergeProjects(IEnumerable<DesignProjectViewModel> selectedProjects)
{
var response = await ConnectingClient.PostAsJsonAsync("api/designProject/mergeProjects", selectedProjects);
if (!response.IsSuccessStatusCode) return new TestObject { MyProperty1=false,MyProperty2="Failed"};
var result = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<TestObject>(result);
}

Can a SignalR Hub receive events from clients? And if so, how?

I have a signalR hub that needs to be able to receive an event from a client and then notify all other clients connected to the hub.
Is that possible?
I want my 'hub' application to be able to receive messages and send them. I can only figure out how to do the sending of messages. Here is what I have now:
Application 1-- Hub
Startup class:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddSignalR().AddHubOptions<EventsHub>(options =>
{
options.HandshakeTimeout = TimeSpan.FromMinutes(5);
options.EnableDetailedErrors = true;
});
services.AddTransient(typeof(BusinessLogic.EventsBusinessLogic));
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
app.UseSignalR((configure) =>
{
configure.MapHub<EventsHub>("/hubs/events", (options) =>
{
});
});
}
Set Up of the Hub in Application 1
public class EventsHub : Hub
{
public EventsHub()
{
}
public override Task OnConnectedAsync()
{
if (UserHandler.ConnectedIds.Count == 0)
{
//Do something on connect
}
UserHandler.ConnectedIds.Add(Context.ConnectionId);
Console.WriteLine("Connection:");
return base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
//Do something on Disconnect
}
public static class UserHandler
{
public static HashSet<string> ConnectedIds = new HashSet<string>();
}
}
BusinessLogic:
public class EventsBusinessLogic
{
private readonly IHubContext<EventsHub> _eventsHub;
public EventsBusinessLogic(IHubContext<EventsHub> eventsHub)
{
_eventsHub = eventsHub;
}
public async Task<Task> EventReceivedNotification(ProjectMoonEventLog eventInformation)
{
try
{
await _eventsHub.Clients.All.SendAsync("NewEvent", SomeObject);
}
catch (Exception e)
{
throw new Exception(e.Message);
}
}
}
In the second application, that listens for events or messages from the hub:
Startup.cs
private static void ConfigureAppServices(IServiceCollection services, string Orale, string Sql)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddOptions();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
//set up of singletons and transients
services.AddHostedService<Events.EventingHubClient>();
}
The ClientHub to connect to application 1:
public class EventingHubClient : IHostedService
{
private HubConnection _connection;
public EventingHubClient()
{
_connection = new HubConnectionBuilder()
.WithUrl("http://localhost:61520/hubs/events")
.Build();
_connection.On<Event>("NewEvent",
data => _ = EventReceivedNotification(data));
}
public async Task<Task> EventReceivedNotification(Event eventInformation)
{
try
{
//Do something when the event happens
return Task.CompletedTask;
}
catch (Exception e)
{
throw new Exception(e.Message);
}
}
public async Task StartAsync(CancellationToken cancellationToken)
{
// Loop is here to wait until the server is running
while (true)
{
try
{
await _connection.StartAsync(cancellationToken);
Console.WriteLine("Connected");
break;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
await Task.Delay(100);
}
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
return _connection.DisposeAsync();
}
}
This works, but now I want application 2 to be able to send a message to application 1? So I need a similar piece of code as in the EventsBusinessLogic class in application2 to send messages to application 1.
I hope this is clear enough? Is this the purpose of SignalR?
Please refer to signalR documentation signalR documentation for .net client
I guess in your Hub method like this
public async Task SendTransaction(Transaction data)
{
await Clients.All.SendAsync("TransactionReceived", data);
}
Then add methods in client side
in constructor add
connection.On<Transaction>("TransactionReceived", (data) =>
{
this.Dispatcher.Invoke(() =>
{
var transactionData = data;
});
});
and then SendTransaction expected on server
private async void SendTransaction(Transaction data)
{
try
{
await connection.InvokeAsync("SendTransaction", data);
}
catch (Exception ex)
{
//
throw
}
}

Correct way to return HttpResponseMessage as IActionResult in .Net Core 2.2

In .Net Core 2.2. I am creating a API Controller that routes the request to another Http endpoint based on payload.
[Route("api/v1")]
public class RoutesController : Controller
{
[HttpPost]
[Route("routes")]
public async Task<IActionResult> Routes([FromBody]JObject request)
{
var httpClient = new HttpClient();
// here based on request httpCLient will make `POST` or `GET` or `PUT` request
// and returns `Task<HttpResponseMessage>`. Lets assume its making `GET`
// call
Task<HttpResponseMessage> response = await
httpClient.GetAsync(request["resource"]);
/* ??? what is the correct way to return response as `IActionResult`*/
}
}
based on SO post i can do this
return StatusCode((int)response.StatusCode, response);
However i am not sure sending HttpResponseMessage as ObjectResult is correct way.
I also want to make sure content negotiation will work.
Update 7/25/2022
Updated the correct answer
public class HttpResponseMessageResult : IActionResult
{
private readonly HttpResponseMessage _responseMessage;
public HttpResponseMessageResult(HttpResponseMessage responseMessage)
{
_responseMessage = responseMessage; // could add throw if null
}
public async Task ExecuteResultAsync(ActionContext context)
{
var response = context.HttpContext.Response;
if (_responseMessage == null)
{
var message = "Response message cannot be null";
throw new InvalidOperationException(message);
}
using (_responseMessage)
{
response.StatusCode = (int)_responseMessage.StatusCode;
var responseFeature = context.HttpContext.Features.Get<IHttpResponseFeature>();
if (responseFeature != null)
{
responseFeature.ReasonPhrase = _responseMessage.ReasonPhrase;
}
var responseHeaders = _responseMessage.Headers;
// Ignore the Transfer-Encoding header if it is just "chunked".
// We let the host decide about whether the response should be chunked or not.
if (responseHeaders.TransferEncodingChunked == true &&
responseHeaders.TransferEncoding.Count == 1)
{
responseHeaders.TransferEncoding.Clear();
}
foreach (var header in responseHeaders)
{
response.Headers.Append(header.Key, header.Value.ToArray());
}
if (_responseMessage.Content != null)
{
var contentHeaders = _responseMessage.Content.Headers;
// Copy the response content headers only after ensuring they are complete.
// We ask for Content-Length first because HttpContent lazily computes this
// and only afterwards writes the value into the content headers.
var unused = contentHeaders.ContentLength;
foreach (var header in contentHeaders)
{
response.Headers.Append(header.Key, header.Value.ToArray());
}
await _responseMessage.Content.CopyToAsync(response.Body);
}
}
}
You can create a custom IActionResult that will wrap transfere logic.
public async Task<IActionResult> Routes([FromBody]JObject request)
{
var httpClient = new HttpClient();
HttpResponseMessage response = await httpClient.GetAsync("");
// Here we ask the framework to dispose the response object a the end of the user resquest
this.HttpContext.Response.RegisterForDispose(response);
return new HttpResponseMessageResult(response);
}
public class HttpResponseMessageResult : IActionResult
{
private readonly HttpResponseMessage _responseMessage;
public HttpResponseMessageResult(HttpResponseMessage responseMessage)
{
_responseMessage = responseMessage; // could add throw if null
}
public async Task ExecuteResultAsync(ActionContext context)
{
context.HttpContext.Response.StatusCode = (int)_responseMessage.StatusCode;
foreach (var header in _responseMessage.Headers)
{
context.HttpContext.Response.Headers.TryAdd(header.Key, new StringValues(header.Value.ToArray()));
}
if(_responseMessage.Content != null)
{
using (var stream = await _responseMessage.Content.ReadAsStreamAsync())
{
await stream.CopyToAsync(context.HttpContext.Response.Body);
await context.HttpContext.Response.Body.FlushAsync();
}
}
}
}
ASP.NET Core has the return object RedirectResult to redirect the caller.
Simply wrap the response in Ok() Action return type:
return Ok(response)
so your code would look something like:
[Route("api/v1")]
public class RoutesController : Controller
{
[HttpPost]
[Route("routes")]
public async Task<IActionResult> Routes([FromBody]JObject request)
{
var httpClient = new HttpClient();
Task<HttpResponseMessage> response = await httpClient.GetAsync(request["resource"]);
return Ok(response);
}
}
More info here: https://learn.microsoft.com/en-us/aspnet/core/web-api/action-return-types?view=aspnetcore-3.1