.NET Core 2.1
NLog 4.6
xUnit 2.3.1
I have class library with xUnit that calls a separate library that contains REST-based APIs that is responsible for creating various logs for the system.
Since the unit test class library calls the REST-based API controller directly, the class's Startup class isn't loaded so I don't believe NLog is being configured. This will need to be done within the unit test class library but I cannot seem to figure that out.
I am able to load the REST-based API nlog configuration from the calling library and then execute NLogs directly from the LogManager but the NLog implementation explicitly used within the REST-based API does not log nor does any error occur.
If I use a soap client such as SOAPUI and call the REST's class library, the logs are created as expected. This means the unit test class library isn't configuring logging correctly.
// Unit Test's base class for wiring up DI and other configuration including Logging
public BaseTests()
{
// Configuration
string loggingServiceAPIPath = #"../../../../../../LoggingService/API/CM.LoggingService.API";
var builder = new ConfigurationBuilder().SetBasePath(Path.GetFullPath(loggingServiceAPIPath)).AddJsonFile("appsettings.json");
var configuration = builder.Build();
// Configure logging
LogManager.Configuration = new XmlLoggingConfiguration(Path.GetFullPath($"{ loggingServiceAPIPath }/nlog.config"));
// Application-Wide Services
IServiceCollection services = new ServiceCollection();
services.AddMvc();
services.AddLogging();
services.AddSingleton(configuration);
services.AddSingleton<ILoggerFactory, LoggerFactory>();
services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));
services.AddSingleton<IMemoryCache, MemoryCache>();
services.AddSingleton<ILoggingServiceController, LoggingServiceController>();
services.AddApplicationServices();
services.AddOptions();
services.ConfigureConfigServerClientOptions(configuration);
services.AddConfiguration(configuration);
services.Configure<ConfigServerData>(configuration);
this._serviceProvider = services.BuildServiceProvider();
// Persist configuration
IMemoryCache iMemoryCache = this._serviceProvider.GetService<IMemoryCache>();
IOptionsSnapshot<ConfigServerData> iConfigServerData = this._serviceProvider.GetService<IOptionsSnapshot<ConfigServerData>>();
if (iMemoryCache != null && iConfigServerData != null) { iMemoryCache.Set(CM.Common.Constants.ConfigKey, iConfigServerData.Value); }
}
// Unit Test being called from a class library
[Fact]
public async void Test_LogDebugSuccess()
{
LoggingServiceRequest request = new LoggingServiceRequest
{
ErrorException = new Exception(),
Message = System.Reflection.MethodBase.GetCurrentMethod().Name
};
// This is not capturing NLog probably due to not being called in a hosted environment.
var result = await
this._iLoggingServiceController.LogDebug(request);
// Assert
Assert.Null(result as NotFoundObjectResult);
var okObjectResult = result as OkObjectResult;
Assert.True((okObjectResult != null &&
okObjectResult.StatusCode.GetValueOrDefault(0) == Convert.ToInt32(System.Net.HttpStatusCode.OK)), "Log was not created.");
}
// LoggingService
public class Program
{
/// <summary>
/// Main
/// </summary>
/// <param name="args">Arguments</param>
public static void Main(string[] args)
{
// NLog: setup the logger first to catch all errors
var logger = NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
try
{
logger.Debug("Init Main");
Program.BuildWebHost(args).Run();
}
catch (Exception ex)
{
logger.Error(ex, $"Stopped program because of exception: { ex.Message }");
throw;
}
finally
{
// Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux)
NLog.LogManager.Shutdown();
}
}
/// <summary>
/// Build WebHost
/// </summary>
/// <param name="args">Arguments</param>
/// <returns>WebHost interface</returns>
public static IWebHost BuildWebHost(string[] args)
{
try
{
var config = WebHost.CreateDefaultBuilder(args)
.CaptureStartupErrors(false)
.AddConfigServer()
.UseStartup<Startup>()
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.SetMinimumLevel(LogLevel.Trace);
})
.UseNLog() // NLog: setup NLog for Dependency injection
.Build();
return config;
}
catch (Exception ex)
{
throw ex;
}
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(
config =>
{
config.Filters.Add(typeof(CustomExceptionFilter));
}
);
// Add memory cache
services.AddMemoryCache();
services.AddMvc();
services.AddCors(o => o.AddPolicy("corspolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod()));
services.AddSingleton(this.Configuration);
// Application-Wide Services
services.AddApplicationServices();
// Configuration
services.AddOptions();
services.ConfigureConfigServerClientOptions(this.Configuration);
services.AddConfiguration(this.Configuration);
services.Configure<ConfigServerData>(this.Configuration);
// Configure Swagger
services.AddSwaggerGen(s =>
{
s.SwaggerDoc("v1", new Info { Title = "CM LoggingService APIs", Version = "V1" });
});
}
public void Configure(IApplicationBuilder iApplicationBuilder, IHostingEnvironment iHostingEnvironment, IConfigurationManager iConfigurationManager, IMemoryCache iMemoryCache, IApplicationLifetime iApplicationLifetime, ILogger<LoggingServiceController> iLogger)
{
if (iHostingEnvironment.IsDevelopment() == true)
{
iApplicationBuilder.UseDeveloperExceptionPage();
iApplicationBuilder.UseStatusCodePages();
iApplicationBuilder.UseDatabaseErrorPage();
iApplicationBuilder.UseBrowserLink();
}
if (iHostingEnvironment.IsProduction() == false)
{
// Swagger - API Documentation
iApplicationBuilder.UseSwagger();
iApplicationBuilder.UseSwaggerUI(s =>
{
s.SwaggerEndpoint("./v1/swagger.json", "CM LoggingService APIs");
});
}
// Persist Steeltoe configuration
iConfigurationManager.Init();
if (iMemoryCache != null && iConfigurationManager != null) { iMemoryCache.Set(CM.Common.Constants.MEMORYCACHE_CONFIGURATIONMANAGER_KEY, iConfigurationManager); }
iConfigurationManager.LogConfiguration();
// Configure global exception handler
iApplicationBuilder.ConfigureExceptionHandler(iLogger);
iApplicationBuilder.UseMvc();
// Application Events
iApplicationLifetime.ApplicationStarted.Register(this.OnApplicationStarted);
iApplicationLifetime.ApplicationStopped.Register(this.OnApplicationStopping);
}
public class LoggingServiceController : Controller, ILoggingServiceController
{
private readonly ILogger<LoggingServiceController> _iLogger = null;
private readonly ILoggingServiceDomainController _iLoggingServiceDomainController = null;
public LoggingServiceController(ILogger<LoggingServiceController> iLogger, ILoggingServiceDomainController iLoggingServiceDomainController)
{
this._iLogger = iLogger;
this._iLoggingServiceDomainController = iLoggingServiceDomainController;
}
[HttpPost("LogError")]
public async Task<IActionResult> LogError([FromBody] LoggingServiceRequest request)
{
bool result = false;
try
{
// Validation
if (ModelState.IsValid == false)
{
this._iLogger.LogError($"{ CM.Common.ExceptionHandling.ExceptionTypes.VALIDATION }: { typeof(LoggingServiceRequest).Name } (request) is not valid.");
return BadRequest();
}
// Log
result = this._iLogger.LogError(request.ErrorException, request.Message, request.Args);
if (result == false) { return NotFound(); }
}
catch (Exception ex)
{
this._iLogger.LogError(ex, $"{ CM.Common.ExceptionHandling.ExceptionTypes.UNSPECIFIED }: { ex.Message }");
}
return Ok(result);
}
}
Related
We wrote our own simple execution strategy to retry saving any data using our DbContext when it runs into a table lock timeout.
public class RetryTransactionExecutionStrategy : DbExecutionStrategy
{
public RetryTransactionExecutionStrategy() : base()
{
}
protected override bool ShouldRetryOn(Exception exception)
{
while (exception != null)
{
if (exception is MySqlException ex
&& ex.Number == 1205) // Deadlock error code
{
return true;
}
exception = exception.InnerException;
}
return false;
}
}
We register it by using the DbConfig class, in the same folder as the context class.
public class DbConfig : DbConfiguration
{
public DbConfig()
{
SetExecutionStrategy(MySqlProviderInvariantName.ProviderName, () => new RetryTransactionExecutionStrategy());
}
}
Now most regular usage of the context will use the retry execution strategy. However, transactions are a more special case. Microsoft mentions usage of them in their documentation, and tells the user to manually call the execution strategy, like this:
var executionStrategy = new RetryTransactionExecutionStrategy();
executionStrategy.Execute(() =>
{
using (PigDbAccountEntities pigDbAccountEntities = new PigDbAccountEntities())
{
using (var dbtransaction = pigDbAccountEntities.Database.BeginTransaction())
{
try
{
//work on some data
pigDbAccountEntities.SaveChanges();
//work on some more data
pigDbAccountEntities.SaveChanges();
//work on even more data
pigDbAccountEntities.SaveChanges();
dbtransaction.Commit();
isSaved = true;
}
catch (Exception ex)
{
dbtransaction.Rollback();
Logger.Instance.Log(LogLevel.ERROR, LogSource.DB, "error in AccountEntityManager.SaveApplicationUser", ex);
}
}
}
});
And yet we still get this error message:
The configured execution strategy 'RetryTransactionExecutionStrategy' does not support user initiated transactions. See http://go.microsoft.com/fwlink/?LinkId=309381 for additional information.
Any idea on what to do/check?
I am doing user authentication in my startup.cs. I need to query my database using the OpenIDConnect claims info. This is what I have done but don't know how to get the connection to work. I tried injecting the db query constructor at the top of the startup.cs like this and then calling the query as follows:
public class Startup
{
protected IAdoSqlService _adoSqlService;
public Startup(IConfiguration configuration, IAdoSqlService adoSqlService)
{
Configuration = configuration;
_adoSqlService = adoSqlService;
}
public void ConfigureServices(IServiceCollection services)
{
// do ConfigureServices stuff
options.Events = new OpenIdConnectEvents()
{
OnTokenValidated = async ctx =>
{
// This is the ClaimsIdentity created by OpenID Connect, you can add claims to it directly
ClaimsIdentity claimsIdentity = ctx.Principal.Identities.FirstOrDefault();
string userntid = claimsIdentity.Claims.FirstOrDefault(c => c.Type == "preferred_username").Value;
//How do I call the database to run the following query
int isUser = _adoSqlService.isUser(userntid);
if (isUser > 0)
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "user"));
}
else
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "not authorized"));
}
}
}
//More stuff
}
}
When I run the above, it errors in program.cs before even running with the following error
System.InvalidOperationException: 'Unable to resolve service for type 'XXXX.Services.IAdoSqlService' while attempting to activate 'XXXX.Startup'.'
So how do I make the call _adoSqlService.isUser(userntid); to the database?
I am NOT using EF.
Solution
I figured this out by doing the following:
I moved most of my services to the top of the ConfigureServices section (based on something that #qudus said) before I performed my authentication.
I removed the database injection code from the top of the startup.cs.
Lastly I changed the OnTokenValidated to use the following:
ctx.HttpContext.RequestServices.GetRequiredService();
Here is the code:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
internal static IConfiguration Configuration { get; private set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
var connectionSection = Configuration.GetSection("ConnectionStrings");
services.Configure<ConnectionStrings>(connectionSection);
services.AddScoped<IAdoSqlService, AdoSqlService>();
services.AddControllersWithViews();
services.AddHttpContextAccessor();
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSession();
// Load the Federation configuration section from app settings
var federationConfig = Configuration.GetSection("Federation");
services.Configure<CookiePolicyOptions>(options =>
{
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromHours(2);//default is 14days
options.SlidingExpiration = true;// default
options.AccessDeniedPath = "/Error/AuthenticateError";// set a custom error access denied error page. this would need to be created/handled in your app.
})
.AddOpenIdConnect(options =>
{
//Set Options here......
//optional customizations to the auth and failure events
options.Events = new OpenIdConnectEvents()
{
OnRedirectToIdentityProvider = context =>
{
return Task.CompletedTask;
},
OnRemoteFailure = context =>
{
// handle an error response from Federation and redirect the user to a custom error page instead
context.Response.Redirect("/Error/401");
context.HandleResponse();
return Task.CompletedTask;
},
OnTokenValidated = async ctx =>
{
// This is the ClaimsIdentity created by OpenID Connect, you can add claims to it directly
ClaimsIdentity claimsIdentity = ctx.Principal.Identities.FirstOrDefault();
string userntid = claimsIdentity.Claims.FirstOrDefault(c => c.Type == "preferred_username").Value;
string username = claimsIdentity.Claims.FirstOrDefault(c => c.Type == "name").Value;
int isUser = 0;
int isAdmin = 0;
try
{
var db = ctx.HttpContext.RequestServices.GetRequiredService<IAdoSqlService>();
isUser = db.isUser(userntid);
isAdmin = db.isAdmin(userntid);
}
catch (Exception ex)
{
string error = ex.Message;
}
AppHttpContext.Current.Session.SetString("IsUser", "false");
if (isUser > 0)
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "user"));
AppHttpContext.Current.Session.SetString("IsUser", "true");
}
AppHttpContext.Current.Session.SetString("IsUserAdmin", "false");
if (isAdmin > 0)
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "admin"));
AppHttpContext.Current.Session.SetString("IsUserAdmin", "true");
}
if (isUser == 0 && isAdmin == 0)
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "not authorized"));
}
}
};
});
Solution
I figured this out by doing the following:
I moved most of my services to the top of the ConfigureServices section (based on something that #qudus said) before I performed my authentication.
I removed the database injection code from the top of the startup.cs.
Lastly I changed the OnTokenValidated to use the following:
ctx.HttpContext.RequestServices.GetRequiredService();
Here is the code:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
internal static IConfiguration Configuration { get; private set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
var connectionSection = Configuration.GetSection("ConnectionStrings");
services.Configure<ConnectionStrings>(connectionSection);
services.AddScoped<IAdoSqlService, AdoSqlService>();
services.AddControllersWithViews();
services.AddHttpContextAccessor();
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSession();
// Load the Federation configuration section from app settings
var federationConfig = Configuration.GetSection("Federation");
services.Configure<CookiePolicyOptions>(options =>
{
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromHours(2);//default is 14days
options.SlidingExpiration = true;// default
options.AccessDeniedPath = "/Error/AuthenticateError";// set a custom error access denied error page. this would need to be created/handled in your app.
})
.AddOpenIdConnect(options =>
{
//Set Options here......
//optional customizations to the auth and failure events
options.Events = new OpenIdConnectEvents()
{
OnRedirectToIdentityProvider = context =>
{
return Task.CompletedTask;
},
OnRemoteFailure = context =>
{
// handle an error response from Federation and redirect the user to a custom error page instead
context.Response.Redirect("/Error/401");
context.HandleResponse();
return Task.CompletedTask;
},
OnTokenValidated = async ctx =>
{
// This is the ClaimsIdentity created by OpenID Connect, you can add claims to it directly
ClaimsIdentity claimsIdentity = ctx.Principal.Identities.FirstOrDefault();
string userntid = claimsIdentity.Claims.FirstOrDefault(c => c.Type == "preferred_username").Value;
string username = claimsIdentity.Claims.FirstOrDefault(c => c.Type == "name").Value;
int isUser = 0;
int isAdmin = 0;
try
{
var db = ctx.HttpContext.RequestServices.GetRequiredService<IAdoSqlService>();
isUser = db.isUser(userntid);
isAdmin = db.isAdmin(userntid);
}
catch (Exception ex)
{
string error = ex.Message;
}
AppHttpContext.Current.Session.SetString("IsUser", "false");
if (isUser > 0)
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "user"));
AppHttpContext.Current.Session.SetString("IsUser", "true");
}
AppHttpContext.Current.Session.SetString("IsUserAdmin", "false");
if (isAdmin > 0)
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "admin"));
AppHttpContext.Current.Session.SetString("IsUserAdmin", "true");
}
if (isUser == 0 && isAdmin == 0)
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "not authorized"));
}
}
};
});
I want to read data from database. For this I create a query and queryhandler classes
QueryHandler
public class OrderGetQueryHandler: IQueryHandler<OrderGetQuery, OrderDTO>
{
private readonly GoodWillWebDbContext _context;
private readonly IQueryDispatcher _queryDispatcher;
public OrderGetQueryHandler(GoodWillWebDbContext context, IQueryDispatcher queryDispatcher)
{
_context = context;
_queryDispatcher = queryDispatcher;
}
private bool CheckPartnerBlock(BlockTypes blockType, decimal debtOverdue, bool payOff)
{
if (blockType == BlockTypes.Block)
return true;
if (blockType == BlockTypes.NotBlock)
return false;
if (blockType == BlockTypes.PreliminaryPayment)
return payOff;
return debtOverdue <= 0;
}
public async Task<OrderDTO> HandleAsync(OrderGetQuery query)
{
var order = await _context.Orders.FindAsync(query.OrderID);
if (order != null)
{
var getCustomerTask = _context.Partners.FindAsync(order.CustomerID).AsTask();
var getCuratorTask = _context.Users.FindAsync(order.CuratorID).AsTask();
var getPaymentTask = _context.Payments.OrderByDescending(x => x.PaymentID).FirstOrDefaultAsync(x => x.CustomerID == order.CustomerID);
var getOrderLinesTask =
_queryDispatcher.HandleAsync<OrderLinesGetQuery, OrderLineDTO[]>(
new OrderLinesGetQuery(query.OrderID));
await Task.WhenAll(getCustomerTask, getCuratorTask, getOrderLinesTask, getPaymentTask);
var priceRange = await _context.PriceRanges.FindAsync(getCustomerTask.Result.PriceRangeID);
return new OrderDTO
(
order.OrderID,
getCustomerTask.Result.Name,
getOrderLinesTask.Result,
order.CustomerID,
order.OrderStateID,
order.CanDelete,
order.CreationDate,
getPaymentTask.Result.DebtBank,
getPaymentTask.Result.DebtOverdue,
this.CheckPartnerBlock(getCustomerTask.Result.BlockTypeID, getPaymentTask.Result.DebtOverdue, order.PayOff),
priceRange.Name,
order.ReservationDate,
Mapper.Convert<DeliveryInfoDTO, BaseEntities.Entities.Sales.Order>(order)
);
}
throw new NullReferenceException();
}
}
this queryhandler i use in ASP.NET WEB Application. My startup class is
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
string connection = Configuration.GetConnectionString("DefaultConnection");
services.AddDbContext<GoodWillWebDbContext>(options =>
options.UseSqlServer(connection), ServiceLifetime.Transient);
services.AddScoped<IQueryHandler<OrdersGetQuery, BaseEntities.DTO.Sales.Order.OrderDTO[]>, OrdersGetQueryHandler>();
services.AddScoped<IQueryHandler<OrderGetQuery, Sales.Queries.DTO.Order.OrderDTO>, OrderGetQueryHandler>();
services.AddScoped<ICommandDispatcher, CommandDispatcher>();
services.AddScoped<IQueryDispatcher, QueryDispatcher>();
}
// 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.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
I set ServiceLifetime.Transient for my context, but I still get an exception: InvalidOperationException A second operation started on this context before a previous operation completed.
What's wrong?
It seems you're running multiple operations on the context without waiting for the previous ones to end, which EF doesn't like:
var getCustomerTask = _context.Partners.FindAsync(order.CustomerID).AsTask();
var getCuratorTask = _context.Users.FindAsync(order.CuratorID).AsTask();
var getPaymentTask = _context.Payments.OrderByDescending(x => x.PaymentID).FirstOrDefaultAsync(x => x.CustomerID == order.CustomerID);
Either make these call sync or use the await keyword.
Below is verticle
package com.api.redis.gateway.verticle;
import java.util.UUID;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
import io.vertx.redis.RedisClient;
import io.vertx.redis.RedisOptions;
public class SimpleRestChild extends SimpleRestServer{
RedisClient client;
#Override
public void start() {
// TODO Auto-generated method stub
super.start();
client = RedisClient.create(vertx, new RedisOptions().setHost("127.0.0.1").setPort(6379));
client.subscribe("channelForServiceToPublish", handler -> {
if(handler.succeeded())
System.out.println("SimpleRestServer subscibed to the channel successfully");
});
}
public void handleSubscription(RoutingContext routingContext) {
JsonObject requestAsJson = routingContext.getBodyAsJson();
requestAsJson.put("uuid", getUUID());
// this client object is null.
client.set("request", requestAsJson.toString(), handler ->{
System.out.println("Simple server is setting value to redis client");
if(handler.succeeded()) {
System.out.println("Key and value is stored in Redis Server");
}else if(handler.failed()) {
System.out.println("Key and value is failed to be stored on Redis Server with cause : "+ handler.cause().getMessage());
}
});
client.publish("channelForServerToPublish", "ServiceOne", handler -> {
if(handler.succeeded()) {
System.out.println("Simple Server published message successfully");
}else if(handler.failed()) {
System.out.println("Simple Server failed to published message");
}
});
routingContext.vertx().eventBus().consumer("io.vertx.redis.channelForServiceToPublish", handler -> {
client.get("response", res ->{
if(res.succeeded()) {
JsonObject responseAsJson = new JsonObject(res.result());
if(responseAsJson.getString("uuid").equalsIgnoreCase(requestAsJson.getString("uuid"))) {
routingContext.response().setStatusCode(200).end(res.result());
}
}else if(res.failed()) {
System.out.println("Failed to get message from Redis Server");
routingContext.response().setStatusCode(500).end("Server Error ");
}
});
});
}
private String getUUID() {
UUID uid = UUID.randomUUID();
return uid.toString();
}
}
And below is the main verticle from where the above verticle is getting deployed and on any request to httpserver it's hanlder method is getting called.
package com.api.redis.gateway.verticle;
import io.vertx.core.AbstractVerticle;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.redis.RedisClient;
import io.vertx.redis.RedisOptions;
public class SimpleRestServer extends AbstractVerticle{
#Override
public void start(){
int http_port = 9001;
vertx.deployVerticle("com.api.redis.gateway.verticle.SimpleRestChild", handler -> {
if(handler.succeeded()) {
System.out.println(" SimpleRestChild deployed successfully");
}
});
Router router = Router.router(vertx);
router.route().handler(BodyHandler.create());
SimpleRestChild child = null;
try {
child = (SimpleRestChild) Class.forName("com.api.redis.gateway.verticle.SimpleRestChild").newInstance();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
e.printStackTrace();
}
router.route("/subscription").handler(child::handleSubscription);
vertx.createHttpServer().requestHandler(router::accept).listen(http_port);
System.out.println("Server started at port : " + http_port);
}
}
When handleSubscription is getting called for any "/subscription" request. client object is coming as null.
As per my understanding two objects are getting created here. One with start() and other not having start().
I want to initialize Redisclient once.And use this object when handleSubscription() will get called for any request to "/subscription".
How to achieve this ?
How to fix this problem.
the requests may be coming in before the client initialization is actually complete.
AbstractVerticle has two variations of start():
start(), and
start(Future<Void> startFuture)
the overloaded version with the Future parameter should be used to perform potentially long-running initializations that are necessary to do before the Verticle can be considered deployed and ready. (there's a section dedicated to this topic in the docs).
so you might try changing your code as follows:
public class SimpleRestChild extends SimpleRestServer {
RedisClient client;
#Override
public void start(Future<Void> startFuture) {
client = ...
// important point below is that this Verticle's
// deployment status depends on whether or not
// the client initialization succeeds
client.subscribe("...", handler -> {
if(handler.succeeded()) {
startFuture.complete();
} else {
startFuture.fail(handler.cause());
}
);
}
}
and:
public class SimpleRestServer extends AbstractVerticle {
#Override
public void start(Future<Void> startFuture) {
int http_port = 9001;
vertx.deployVerticle("...", handler -> {
// if the child Verticle is successfully deployed
// then move on to completing this Verticle's
// initialization
if(handler.succeeded()) {
Router router = ...
...
// if the server is successfully installed then
// invoke the Future to signal this Verticle
// is deployed
vertx.createHttpServer()
.requestHandler(router::accept)
.listen(http_port, handler -> {
if(handler.succeeded()) {
startFuture.complete();
} else {
startFuture.fail(handler.cause());
}
});
} else {
startFuture.fail(handler.cause());
}
}
using this type of approach, your Verticles will only service requests when all their dependent resources are fully initialized.
I am trying to inject dependencies into a Web Api Controller.
I created an own IHttpControllerActivator class and replaced the default one in lobalConfiguration.
public class SimpleASPWebAPIContainer : IHttpControllerActivator
{
private readonly CompositionContainer container;
public SimpleASPWebAPIContainer(CompositionContainer compositionContainer)
{
container = compositionContainer;
}
public IHttpController Create(System.Net.Http.HttpRequestMessage request, System.Web.Http.Controllers.HttpControllerDescriptor controllerDescriptor, Type controllerType)
{
if (controllerType != null)
{
var export = container.GetExports(controllerType, null, null).FirstOrDefault();
IHttpController result = null;
if (null != export)
{
result = export.Value as IHttpController;
}
else
{
//result = base.GetControllerInstance(requestContext, controllerType);
//container.ComposeParts(result);
}
return result;
}
else
{
return null;
}
}
public void Dispose()
{
if (container != null)
container.Dispose();
}
}
var apiSimpleContainer = new SimpleASPWebAPIContainer(container);
System.Web.Http.GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), apiSimpleContainer);
But when the client app is calling a controller method the IHttpControllerActivation Create method is not invoked.
Anybody can help me?
It was a very silly mistake.
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
ConfigureOAuth(app);
MefConfig.RegisterMef(config);
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
AutoMapperConfig.InitAutoMapper();
}
I should have to used the new HttoConfiguration instance to replace default IHttpControllerActivator instead of System.Web.Http.GlobalConfiguration.Configuration.