EasyNetQ (AMQP) Single Application Connection In ASP.NET? - asp.net

Community:
I'm struggling to figure out how to create a single AMQP connection that lives with my ASP.NET application lifecycle in ASP.NET using .NET Core 2.1. After researching, I've found lots of references to using a single AMQP connection for the whole application as they are expensive and slow to create and I was headed down the road of creating the connection using DI but it appears my approach is flawed, I can't seem to identify which interface I need to add as a singleton...
public void ConfigureServices(IServiceCollection services)
{
var sqlConnectionStringBuilder = new SqlConnectionStringBuilder(Configuration.GetConnectionString("DefaultConnection"));
var envSQL = Environment.GetEnvironmentVariable("ASPNETCORE_SQL_SERVER");
if (envSQL != null)
sqlConnectionStringBuilder.DataSource = envSQL;
services.AddSingleton<IMessageBusService, MessageBusService>();
services.AddSingleton<EasyNetQ.IAdvancedBus, RabbitAdvancedBus>();
services.AddSingleton<EasyNetQ.IConnectionFactory, ConnectionFactoryWrapper>();
services.AddMvc();
}
Adding the above interfaces works but I get an error about ConnectionConfiguration service not being locatable. Is this the right direction or is there a more proper way to create a single application once EasyNetQ connection in ASP.NET core?

You can use AutoSubcriber in .net core
and use the sample code here.
add connection to appsettings.json
"MessageBroker": {
"ConnectionString": "host=localhost"
}
then add IBus in ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IBus>(RabbitHutch.CreateBus(Configuration["MessageBroker:ConnectionString"]));
services.AddSingleton(RabbitHutch.CreateBus(Configuration["MessageBroker:ConnectionString"]));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
add class AppBuilderExtension and use extension method for auto subscriber
public static class AppBuilderExtension
{
public static IApplicationBuilder UseSubscribe(this IApplicationBuilder appBuilder, string subscriptionIdPrefix, Assembly assembly)
{
var services = appBuilder.ApplicationServices.CreateScope().ServiceProvider;
var lifeTime = services.GetService<IApplicationLifetime>();
var Bus = services.GetService<IBus>();
lifeTime.ApplicationStarted.Register(() =>
{
var subscriber = new AutoSubscriber(Bus, subscriptionIdPrefix);
subscriber.Subscribe(assembly);
subscriber.SubscribeAsync(assembly);
});
lifeTime.ApplicationStopped.Register(() => Bus.Dispose());
return appBuilder;
}
}
add UseSubscribe in Configure
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseSubscribe("ClientMessageService", Assembly.GetExecutingAssembly());
app.UseHttpsRedirection();
app.UseMvc();
}
then create Producers controller
[Route("api/[controller]")]
[ApiController]
public class ProducersController : ControllerBase
{
private readonly IBus _bus;
public ProducersController(IBus bus)
{
_bus = bus;
}
[HttpGet]
[Route("Send")]
public JsonResult Send()
{
_bus.Publish(new TextMessage { Text = "Send Message from the Producer" });
return new JsonResult("");
}
}
then create Consumers controller
[Route("api/[controller]")]
[ApiController]
public class ConsumersController : ControllerBase, IConsume<TextMessage>
{
[HttpGet]
[Route("Receive")]
public JsonResult Receive()
{
using (var bus = RabbitHutch.CreateBus("host=localhost"))
{
bus.Subscribe<TextMessage>("test", HandleTextMessage);
}
return new JsonResult("");
}
private static void HandleTextMessage(TextMessage textMessage)
{
var item = textMessage.Text;
}
public void Consume(TextMessage message)
{
// code receive message
}
}

Related

Swagger UI and Swagger Doc does not generate query string. .NET 6 Web API with Minimal API, Mediatr, and Swashbuckle

I have build a Web API application using .NET 6, Mediatr, and Swashbuckle ASPNetCore. I am using nTier structure, so there is more than one project for my solutions with my Web API project having reference to a class library project that consist business logic.
The problem is, swagger-ui wont generate query string parameter to be shown on browser while everything else is normal. And another interesting part is, when using Postman, you can pass the query string key and value and it works like a charm.
Here is my request body model from class library project:
using MediatR;
using Microsoft.AspNetCore.Http;
using System.Reflection;
namespace Rest.API.Application
{
public class FindRequest : IRequest<FindResponse>
{
public string firstName { get; init; }
public string lastName { get; set; }
public static ValueTask<FindRequest> BindAsync(HttpContext context, ParameterInfo parameter)
{
FindRequest result = new()
{
firstName = context.Request.Query["firstName"],
lastName = context.Request.Query["lastName"]
};
return ValueTask.FromResult(result);
}
}
}
Here is the Endpoint class from Web API project:
using MediatR;
using Rest.API.Application;
namespace Rest.API.Core
{
public interface IEndpoint
{
void ConfigureApplication(WebApplication app);
}
public class Endpoint : IEndpoint
{
public void ConfigureApplication(WebApplication app)
{
app.MapGet("employee/{id}", async (IMediator mediator, string id) => await mediator.Send(new GetRequest(id)));
app.MapGet("employee", async (IMediator mediator, FindRequest request) => await mediator.Send(request));
}
}
}
My endpoint extension class to wrap all endpoint so I can easily register all endpoint to Program.cs:
using Microsoft.OpenApi.Models;
using System.Reflection;
namespace Rest.API.Core
{
public static class EndpointExtension
{
public static void AddEndpoint(this IServiceCollection service, params Type[] types)
{
var endpoints = new List<IEndpoint>();
foreach (var type in types)
{
endpoints.AddRange(type.Assembly.ExportedTypes
.Where(x => typeof(IEndpoint).IsAssignableFrom(x)
&& !x.IsInterface
&& !x.IsAbstract)
.Select(Activator.CreateInstance)
.Cast<IEndpoint>());
}
service.AddSingleton(endpoints as IReadOnlyCollection<IEndpoint>);
}
public static void UseEndpoint(this WebApplication app)
{
var endpoints = app.Services.GetRequiredService<IReadOnlyCollection<IEndpoint>>();
foreach (var endpoint in endpoints)
{
endpoint.ConfigureApplication(app);
}
}
}
public static class SwaggerExtension
{
public static void ConfigureSwagger(this IServiceCollection services)
{
services.AddEndpointsApiExplorer();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("dev", new OpenApiInfo
{
Title = "Sample Web API Core",
Version = $"DEV-{Environment.Version.Major}.{Environment.Version.Minor}.{DateTime.Now:yyyyMMddHHmmss}",
Description = "Sample Web API"
});
string xmlDocFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
string xmlDocPath = Path.Combine(AppContext.BaseDirectory, xmlDocFile);
c.IncludeXmlComments(xmlDocPath);
});
}
public static void UseSwaggerApp(this WebApplication app)
{
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(x =>
{
x.SwaggerEndpoint("/swagger/dev/swagger.json", "Rest.API.Core");
x.RoutePrefix = "swagger";
x.DocumentTitle = "Sample Web API";
});
}
}
}
public static class CORSExtension
{
public static void ConfigureCORS(this IServiceCollection services)
{
services.AddCors(options =>
{
//NOT FOR PRODUCTION
options.AddPolicy("AllowAnyOrigin", builder =>
{
builder.AllowAnyOrigin();
builder.AllowAnyMethod();
builder.AllowAnyHeader();
});
});
}
}
}
Finally, my Program.cs:
using MediatR;
using Rest.API.Application;
using Rest.API.Core;
using System.Reflection;
var builder = WebApplication.CreateBuilder(args);
#region SERVICES
builder.Services.ConfigureCORS();
builder.Services.AddMediatR(typeof(GetHandler).GetTypeInfo().Assembly);
builder.Services.ConfigureSwagger();
builder.Services.AddEndpoint(typeof(IEndpoint)); //Register all endpoint(controller) that implementing IEndpoint.
#endregion
var app = builder.Build();
#region PIPELINE
app.UseSwaggerApp();
app.UseEndpoint();
app.UseCors("AllowAnyOrigin");
app.UseHttpsRedirection();
#endregion
app.Run();
For the complete sample project you can get from here.
So, any idea how show the query string model on swagger-ui?
Thank You.
There's no way for your model to express how it's being bound from the request. This is a gap being resolved in .NET 7, see https://github.com/dotnet/aspnetcore/issues/40646.
To accomplish this with .NET 6, you can https://www.nuget.org/packages/MinimalApis.Extensions

How to centralize DI code outside of lambda function.cs and instead into dedicated class for better separation of concerns

I have a lambda function and running dotnet core. I want to use DI, however, I dont want to add DI container related code within the default function class, but instead in a separate file.
How can I do DI in a separate class?
Here is how to accomplish within the function class. However, I want to put this into dedicated class for better separation of concerns.
namespace SQSMessageProcessor
{
public class Function
{
private ILambdaConfiguration Configuration { get; }
private IOrdersService OrderService { get; }
public Function()
{
var serviceCollection = new ServiceCollection();
ConfigureServices(serviceCollection);
var serviceProvider = serviceCollection.BuildServiceProvider();
Configuration = serviceProvider.GetService<ILambdaConfiguration>();
OrderService = serviceProvider.GetService<IOrdersService>();
}
private void ConfigureServices(IServiceCollection serviceCollection)
{
serviceCollection.AddTransient<ILambdaConfiguration, LambdaConfiguration>();
serviceCollection.AddTransient<IOrdersService, OrdersService>();
}
public async Task<string> FunctionHandler(SQSEvent sqsEvent, ILambdaContext context)
{
//Console.WriteLine(Configuration.Configuration["hello"]);
Console.WriteLine($"Message ID: {sqsEvent.Records[0].MessageId}");
Console.WriteLine($"Event Source: {sqsEvent.Records[0].EventSource}");
Console.WriteLine($"Record Body Parsed:");
var details = JObject.Parse(sqsEvent.Records[0].Body);
Console.WriteLine(details);
var order = details.ToObject<Order>();
await OrderService.ProcessOrder(order);
return $"Processed {sqsEvent.Records.Count} records.";
}
}
}
If I get your idea right, you can add an extension static class and make DI there
public static class Extensions
{
public static void AddRabbitMq(this IServiceCollection services)
{
services.AddSingleton(typeof(RabbitMqOptions), service =>
{
//...
});
}
}
and then use it in your ConfigureServices method:
public void ConfigureServices(IServiceCollection services)
{
services.AddRabbitMq();
}

.NET Core, SignalR Hub's constructor IHubCallerClients is NULL

I'm trying to implement .NET Core 2.2/SignalR 1.1.0.
In startup:
public void ConfigureServices(IServiceCollection services)
services.AddSignalR();
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("/chatHub");
});
It works smoothly when I apply a one-to-one example.
But I need an architectural change.
My example:
public class ChatHub : Hub
{
ResponseHandler ResponseHandler { get; set; }
public ChatHub()
{
IHubCallerClients hubCallerClients = this.Clients;
ResponseHandler = new ResponseHandler(hubCallerClients);
}
public async Task SendMessage(string user, string message)
{
IHubCallerClients hubCallerClients = this.Clients;
await ResponseHandler.R();
}
}
If I tried to get this.Clients in the constructor it is coming with null data. But if I try to take it in the method, it comes full as expected.
I should get IHubCallerClients in the contructor so that I can forward it to another Response context.
Thanks advance!
OK. I solved the problem by
public class RequestHandler : Hub
{
ResponseHandler ResponseHandler { get; set; }
public RequestHandler(IHubContext<RequestHandler> hubContext)
{
ResponseHandler = new ResponseHandler(hubContext);
}
public async Task SendMessage(string user, string message)
{
await ResponseHandler.R();
}
}
Due to the nature of .net core, context comes to constructor as dependency.
"services.AddSignalR();" we're sure to add it to Scope.
"IHubContext hubContext" In this way, we can collect the contructured object.

Controller constructor does not get called

Hello i am trying to understand why do my requests not enter my api route.They seem to reach the server but they wont fan out in the MVC.
The server is running on: http://localhost:9300
The route i am requesting is : http://localhost:9300/api/getusers
Program
public class Program {
public static void Main(string[] args) {
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) {
var builder = new WebHostBuilder();
builder.UseStartup<Startup>();
var url = Address.Default.ToUrl();
builder.UseKestrel().UseUrls(url);
return builder;
}
}
Startup
public class Startup {
public Startup(IConfiguration configuration) {
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services) {
services.AddOptions();
services.AddMvc();
}
public IConfiguration Configuration;
public void Configure(IApplicationBuilder app) {
Debug.WriteLine("Entered server"); //enters successfully here
app.UseMvc(); //does not enter the controller
}
}
Controller
This is a simple controller with a GET method.The constructor is not invoked at all.Why would this happen?I know it when the server runs the first time ..it does a health check on its routes.
[ApiController]
class UserController : ControllerBase {
private static List<User> users = new List<User> {
new User{Id=0,Age=0,Name="Failed"},
new User{Id=12,Age=33,Name="Daniel"},
new User{Id=13,Age=33,Name="Marian"},
};
public UserController() {
Debug.WriteLine("Controller called"); //does not get called !
}
[HttpGet]
[Route("api/getusers")]
public async Task<HttpResponseMessage> GetUsers() {
await Task.Delay(1000);
return new HttpResponseMessage {
Content = new StringContent(users.ToJson()),
StatusCode = HttpStatusCode.OK
};
}
}
P.S Do i have to add anyything ? What am i missing i followed other implementations closely.
I've created the webapi project using dotnet new webapi.
I've managed to get to the url with the similar configuration by changing the access modifier of a similar controller. Try to add public keyword to the class UserController. So it should be public class UserController
I will provide more information about the configuration of the project if it is necessary and the step above does not help.

ASP.NET vNext global config access

What is the correct/recommended way of accessing the config.json file (or wherever else config is stored) in ASP.NET vNext?
In the Startup class, I set up the config like so:
public class Startup
{
public IConfiguration Configuration { get; set; }
public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
{
var configurationBuilder = new ConfigurationBuilder(appEnv.ApplicationBasePath)
.AddJsonFile("config.json")
.AddEnvironmentVariables();
Configuration = configurationBuilder.Build();
}
But then if I need to access the connection string elsewhere, how do I do it? For example, in the OnConfiguring of an EF context, how do I get the connection string:
protected override void OnConfiguring(EntityOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer( ??? );
I've read that you could use this:
var config = Startup.Configuration
?? new Configuration()
.AddJsonFile("config.json")
.AddEnvironmentVariables();
But (a) Startup is not static and (b) you surely don't want to go rebuilding the configuration setup every time you need it - that's duplicating code everywhere it's used.
I've also read that you should use Dependency Injection, but that link doesn't fully show you how to do it. If my DbContext constructor has an injected parameter, then how do I inject that into a parameterless BaseApiController?
This really seems like a common/simple requirement: After the configuration in Startup, how to I access that configuration elsewhere? This should in documentation/examples everywhere.
Here's what I've got that's working:
Startup
public class Startup
{
public IConfiguration Configuration { get; set; }
public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
{
// create & store the configuration once
var configurationBuilder = new ConfigurationBuilder(appEnv.ApplicationBasePath)
.AddJsonFile("config.json")
.AddEnvironmentVariables();
Configuration = configurationBuilder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<Context>(options => options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
services.AddIdentity<User, IdentityRole>()
.AddEntityFrameworkStores<Context>()
.AddDefaultTokenProviders();
services.AddMvc();
// adding/registering the dbContext for dependency injection as a singleton
services.AddSingleton(s => new Context(Configuration));
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseIdentity();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action}/{id?}",
defaults: new { controller = "Home", action = "Index" });
});
}
}
DbContext
public sealed class Context : IdentityDbContext<IdentityUser>
{
private readonly IConfiguration _config;
public DbSet<Client> Clients { get; set; }
public Context(IConfiguration config)
{
// store the injected config
_config = config;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ForSqlServer().UseIdentity();
base.OnModelCreating(modelBuilder);
}
protected override void OnConfiguring(EntityOptionsBuilder optionsBuilder)
{
// use the injected config
optionsBuilder.UseSqlServer(_config.Get("Data:DefaultConnection:ConnectionString"));
base.OnConfiguring(optionsBuilder);
}
}
Controller
[Route("api/[controller]")]
public class TestController : BaseController
{
private readonly Context _context;
// have the context injected
public TestController(Context context)
{
_context = context;
}
[HttpGet]
public ActionResult Get()
{
return new ObjectResult(_context.Clients.ToList());
}
}
In the first place, you should avoid registering your database context as a singleton. Also passing around the raw IConfiguration interface isn't a good practice.
In stead could create a POCO options class:
public class DbOptions
{
public string ConnectionString { get; set }
}
And populate it in the ConfigureServices method using the section in the config.json:
services.Configure<DbOptions>(Configuration.GetConfigurationSection("Data:DefaultConnection"));
Then you can inject it into your DbContext (and in controllers, etc.):
public sealed class Context : IdentityDbContext<IdentityUser>
{
private readonly DbOptions _options;
public DbSet<Client> Clients { get; set; }
public Context(IOptions<DbOptions> optionsAccessor)
{
// store the injected options
_options = optionsAccessor.Options;
}
// other code..
}

Resources