Impossible to use dependency injection in an Hangfire job - asp.net-core

Context
I use Hangfire (version 1.7.11) as a scheduler. But I can't use proper DI in my jobs.
What works so far
I have no problem scheduling something like this, given the fact SomeConcreteService have a parameterless constructor:
RecurringJob.AddOrUpdate<SomeConcreteService>(jobId, mc => Console.WriteLine(
$"Message from job: {mc.GetValue()}"), "1/2 * * * *");
What does not work
But I get an exception when I try to inject a service into a Hangfire job using what is recommended here: https://docs.hangfire.io/en/latest/background-methods/using-ioc-containers.html
When I try to add a new scheduled job using DI, I get the following exception:
Exception thrown: 'System.InvalidOperationException' in System.Linq.Expressions.dll: 'variable 'mc' of type 'TestHangfire.IMyContract' referenced from scope '', but it is not defined'
The exception occurs a this line:
RecurringJob.AddOrUpdate<IMyContract>(jobId, mc => Console.WriteLine(
$"Message from job {jobId} => {mc.GetValue()}"), "1/2 * * * *");
The problem is so trivial that I am sure I am missing something obvious.
Thanks for helping.
The (nearly) full code
Service:
public interface IMyContract
{
string GetValue();
}
public class MyContractImplementation : IMyContract
{
public string _label;
public MyContractImplementation(string label)
{
_label = label;
}
public string GetValue() => $"{_label}:{Guid.NewGuid()}";
}
2 kinds of activators:
public class ContainerJobActivator : JobActivator
{
private IServiceProvider _container;
public ContainerJobActivator(IServiceProvider serviceProvider) =>
_container = serviceProvider;
public override object ActivateJob(Type type) => _container.GetService(type);
}
public class ScopedContainerJobActivator : JobActivator
{
readonly IServiceScopeFactory _serviceScopeFactory;
public ScopedContainerJobActivator(IServiceProvider serviceProvider)
{
_serviceScopeFactory = serviceProvider.GetService<IServiceScopeFactory>();
}
public override JobActivatorScope BeginScope(JobActivatorContext context) =>
new ServiceJobActivatorScope(_serviceScopeFactory.CreateScope());
private class ServiceJobActivatorScope : JobActivatorScope
{
readonly IServiceScope _serviceScope;
public ServiceJobActivatorScope(IServiceScope serviceScope) =>
_serviceScope = serviceScope;
public override object Resolve(Type type) =>
_serviceScope.ServiceProvider.GetService(type);
}
}
Startup:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddHangfire(configuration => configuration
.SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UseSqlServerStorage("connection string", new SqlServerStorageOptions
{
CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
QueuePollInterval = TimeSpan.Zero,
UseRecommendedIsolationLevel = true,
UsePageLocksOnDequeue = true,
DisableGlobalLocks = true
}));
services.AddHangfireServer();
services.BuildServiceProvider();
services.AddScoped<IMyContract>(i => new MyContractImplementation("blabla"));
// doesn't work either
// services.AddSingleton<IMyContract>(i => new MyContractImplementation("blabla"));
// doesn't work either
// services.AddTransient<IMyContract>(i => new MyContractImplementation("blabla"));
}
public void Configure(
IApplicationBuilder app,
IWebHostEnvironment env,
IServiceProvider serviceProvider)
{
// Just to ensure the service is correctly injected...
Console.WriteLine(serviceProvider.GetService<IMyContract>().GetValue());
// I face the problem for both activators: ScopedContainerJobActivator or ContainerJobActivator
GlobalConfiguration.Configuration.UseActivator(new ContainerJobActivator(serviceProvider));
// GlobalConfiguration.Configuration.UseActivator(new ScopedContainerJobActivator(serviceProvider));
app.UseRouting();
app.UseHangfireDashboard();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync(
JsonSerializer.Serialize(
Hangfire.JobStorage.Current.GetConnection().GetRecurringJobs()
.Select(i => new { i.Id, i.CreatedAt, i.Cron }).ToList()));
});
endpoints.MapGet("/add", async context =>
{
var manager = new RecurringJobManager();
var jobId = $"{Guid.NewGuid()}";
// I GET AN EXCEPTION HERE:
// Exception thrown: 'System.InvalidOperationException' in System.Linq.Expressions.dll: 'variable 'mc' of type 'TestHangfire.IMyContract' referenced from scope '', but it is not defined'
manager.AddOrUpdate<IMyContract>(jobId, mc => Console.WriteLine(
$"Message from job {jobId} => {mc.GetValue()}"), "1/2 * * * *");
// doesn't work either: it's normal, it is just a wrapper of what is above
// RecurringJob.AddOrUpdate<IMyContract>(jobId, mc => Console.WriteLine($"Message from job {jobId} => {mc.GetValue()}"), "1/2 * * * *");
await context.Response.WriteAsync($"Schedule added: {jobId}");
});
});
}
}

I found the issue.
As it was actually the expression that seemed to cause an issue, and given the fact that the other way to add a recurring job is to transmit a type, and a method info, it seemed to me that the problem was caused by an expression that was too evolved. So I changed the approach to have a method of my service that make the whole job by being given a parameter.
Here is the new code that works:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Hangfire;
using Hangfire.SqlServer;
using Hangfire.Storage;
using System.Text.Json;
namespace TestHangfire
{
#region Service
public interface IMyContract
{
void MakeAction(string someText);
}
public class MyContractImplementation : IMyContract
{
public string _label;
public MyContractImplementation(string label)
{
_label = label;
}
public void MakeAction(string someText) => Console.WriteLine($"{_label}:{someText}");
}
#endregion
#region 2 kinds of activators
public class ContainerJobActivator : JobActivator
{
private IServiceProvider _container;
public ContainerJobActivator(IServiceProvider serviceProvider)
{
_container = serviceProvider;
}
public override object ActivateJob(Type type)
{
return _container.GetService(type);
}
}
#endregion
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddHangfire(configuration => configuration
.SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UseSqlServerStorage("Server=localhost,1433;Database=HangfireTest;user=sa;password=xxxxxx;MultipleActiveResultSets=True", new SqlServerStorageOptions
{
CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
QueuePollInterval = TimeSpan.Zero,
UseRecommendedIsolationLevel = true,
UsePageLocksOnDequeue = true,
DisableGlobalLocks = true
}));
services.AddHangfireServer();
services.BuildServiceProvider();
services.AddTransient<IMyContract>(i => new MyContractImplementation("blabla"));
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider)
{
GlobalConfiguration.Configuration.UseActivator(new ContainerJobActivator(serviceProvider));
app.UseRouting();
app.UseHangfireDashboard();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync(JsonSerializer.Serialize(Hangfire.JobStorage.Current.GetConnection().GetRecurringJobs()
.Select(i => new { i.Id, i.CreatedAt, i.Cron }).ToList()));
});
endpoints.MapGet("/add", async context =>
{
var manager = new RecurringJobManager();
var jobId = $"{Guid.NewGuid()}";
manager.AddOrUpdate<IMyContract>(jobId, (IMyContract mc) => mc.MakeAction(jobId), "1/2 * * * *");
await context.Response.WriteAsync($"Schedule added: {jobId}");
});
});
}
}
}

Related

DatabaseContext object is disposed ASP.NET Core

When I try to retrieve data from a table from database using Entity Framework Core in class, I get an exception:
System.ObjectDisposedException: 'Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
My code looks like this:
Startup.cs:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddDbContext<DatabaseContext>(
options => options.UseSqlServer(Configuration.GetConnectionString("DatabaseContextConection")));
services.AddIdentity<ApplicationUser, IdentityRole>(config => {
config.User.RequireUniqueEmail = true;
config.Password.RequiredLength = 8;
config.Password.RequireNonAlphanumeric = false;
config.Password.RequireUppercase = false;
config.Password.RequireLowercase = false;
// config.COO
}).AddEntityFrameworkStores<DatabaseContext>();
services.AddScoped<IDatabaseChangeNotificationService, SqlDependencyService>();
services.AddSignalR();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env,IDatabaseChangeNotificationService notificationService)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthentication();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name:"",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapHub<ChatHub>("/ChatHub");
});
notificationService.Config();
}
}
DatabaseContext.cs:
public class DatabaseContext: IdentityDbContext<ApplicationUser> ,IApplicationDbContext
{
public DatabaseContext(DbContextOptions<DatabaseContext> options):base(options)
{
}
public DbSet<Message> Messages { get; set; }
public new async Task<int> SaveChanges()
{
return await base.SaveChangesAsync();
}
}
SqlDependencyService.cs:
public interface IDatabaseChangeNotificationService
{
void Config();
}
public class SqlDependencyService : IDatabaseChangeNotificationService
{
private UserManager<ApplicationUser> _userManager;
public string FullName;
public string UserId;
private readonly IConfiguration configuration;
private readonly IHubContext<ChatHub> chatHub;
private DatabaseContext _DBContext;
public SqlDependencyService(DatabaseContext DbContext, IConfiguration _configuration, IHubContext<ChatHub> _chatHub, UserManager<ApplicationUser> userManager)
{
_DBContext = DbContext;
configuration = _configuration;
chatHub = _chatHub;
_userManager = userManager;
}
public void Config()
{
TableUsersAvailabilitySensor();
}
private void TableUsersAvailabilitySensor()
{
string connectionString = configuration.GetConnectionString("DatabaseContextConection");
using (var conn = new SqlConnection(connectionString))
{
if (conn.State != System.Data.ConnectionState.Open)
{
conn.Open();
}
using (var cmd = new SqlCommand(#"Select IsActive from [dbo].AspNetUsers", conn))
{
cmd.Notification = null;
SqlDependency dependency = new SqlDependency(cmd);
dependency.OnChange += TableUsersChanged;
SqlDependency.Start(connectionString);
cmd.ExecuteReader();
}
}
}
List<string> AvailableUsers = new List<string>();
private void TableUsersChanged(object sender, SqlNotificationEventArgs e)
{
if (e.Type == SqlNotificationType.Change)
{
string text = checkAvailabilityChanged(e);
if (text == "Updated" || text == "Inserted")
{
var list = _DBContext.Users.Where(a => a.IsActive == true).ToList();
AvailableUsers.AddRange(list.Select(a => a.UserName));
var currentUserId = _userManager.GetUserId(AccountController.currentUser);
var _currentUser = _DBContext.Users.Find(currentUserId);
FullName = _currentUser.FirstName + " " + _currentUser.LastName;
UserId = currentUserId;
chatHub.Clients.Clients(AvailableUsers).SendAsync("AddMeToYourContacts", FullName, UserId);
}
}
TableUsersAvailabilitySensor();
}
private string checkAvailabilityChanged(SqlNotificationEventArgs e)
{
switch (e.Info)
{
case SqlNotificationInfo.Update:
return "Updated";
case SqlNotificationInfo.Delete:
return "Deleted";
case SqlNotificationInfo.Insert:
return "Inserted";
default:
return "Nothing occurred";
}
}
}
The exception is thrown on this line of code:
var list = _DBContext.Users.Where(a => a.IsActive == true).ToList();

Endpoint is null when accessed in middleware during asp.net core 3.1 integration test

I run integration tests for my asp.net core application, the call passes from multiple middle-wares but stops at one of them which has the following line :
var endpoint = context.Features.Get<IEndpointFeature>()?.Endpoint;
var attribute = endpoint?.Metadata.GetMetadata<AllowAHeader>();
The endpoint is null.
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup>
where TStartup : class
{
protected override IHostBuilder CreateHostBuilder()
{
var builder = Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(x =>
{
x.UseStartup<TStartup>().UseTestServer();
});
return builder;
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
builder.ConfigureTestServices(services =>
{
services.RemoveAll<DbContext>();
services.RemoveAll<DbContextOptions>();
foreach (var option in services.Where(s =>
s.ServiceType.BaseType ==
typeof(DbContextOptions)).ToList())
{
services.Remove(option);
}
services.AddDbContext<DbContext>(options =>
{
options.UseInMemoryDatabase("Testing");
});
});
}
}
Here is the test
public class ClientTests : IClassFixture<CustomWebApplicationFactory<TestStartup>>
{
private readonly HttpClient _client;
public ClientTests(CustomWebApplicationFactory<TestStartup> customWebApplicationFactory)
{
_client = customWebApplicationFactory.CreateClient();
}
[Fact]
public async Task GetClients()
{
_client.DefaultRequestHeaders.Add("X-Integration-Testing", "True");
_client.DefaultRequestHeaders.Add("X-Integration-Authroize", "Basic");
var result = await _client.PostAsync("v1/client", null);
}
}
The TestStartup :
public class TestStartup : Startup
{
public TestStartup(IConfiguration configuration)
: base(configuration)
{
}
protected override void ConfigureMiddlewareForIntegrationTest(IApplicationBuilder app)
{
app.UseMiddleware<AuthenticatedTestRequestMiddleware>();
}
}
public class AuthenticatedTestRequestMiddleware
{
public const string TestingHeader = "X-Integration-Testing";
public const string TestingHeaderAuthValueValue = "X-Integration-Authroize";
private readonly RequestDelegate _next;
public AuthenticatedTestRequestMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
if (context.Request.Headers.Keys.Contains(TestingHeader))
{
if (context.Request.Headers.Keys.Contains(TestingHeaderAuthValueValue))
{
var encoded = "Basic " + System.Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes("user" + ":" + "123456"));
context.Request.Headers.Add("Authorization", encoded);
}
}
}
}
In ConfigureWebHostDefaults add:
x.UseHttpSys(opt =>
opt.RequestQueueMode = RequestQueueMode.Create;
)
Have not figured out exactly why it's needed, but I'm guessing it's a bug being the value of RequestQueueMode is 0 by default, same as RequestQueueMode.Create's value.

ASP.NET Core 3.1 SignalR connects but doesnt' work

I am migrating an ASP.NET Web API 2 service to ASP.NET Core 3.1 Web API, and I already resolved all dependencies and problems, but I'm not getting the SignalR.
Strange things:
I can connect but I'm receiving any message:
chrome console connection
The id about this connection is always 0(in asp.net framework, was always an unique code)
I put a custom authorize in the hub:
Code:
using Flow2.Api.Security;
using Microsoft.AspNetCore.SignalR;
namespace Flow2.Api.Hubs
{
[AuthorizeSignalR]
public class HubNotification : Hub
{
public void Notificar()
{
Clients.All.SendAsync("broadcastMessage", "oi", "olá");
}
}
}
With a break point, but the breakpoint is not getting hit: print authorize with breakpoint
My startup configuration:
using System;
using System.Globalization;
using System.IO.Compression;
using System.Runtime.InteropServices;
using DataTables.AspNet.AspNetCore;
using Flow2.Api.Hangfire;
using Flow2.Api.Helpers;
using Flow2.Api.Hubs;
using Flow2.Api.JsonConverters;
using Flow2.Api.Security;
using Flow2.Api.Security.SignalR;
using Flow2.Mappers;
using Flow2.SharedKernel.Config;
using Hangfire;
using Hangfire.SqlServer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Flow2.CrossCutting.Startup;
using Flow2.Domain.Interfaces;
using Flow2.Domain.Outros.Arquivo.Interfaces;
using Flow2.SharedKernel;
using Flow2.SharedKernel.Events;
using Flow2.SharedKernel.Helpers;
using Flow2.SharedKernel.Helpers.Email;
using Hangfire.MemoryStorage;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.Extensibility.Implementation;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.ResponseCompression;
using Microsoft.AspNetCore.SignalR;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Swashbuckle.AspNetCore.SwaggerUI;
namespace Flow2.Api
{
using System.Linq;
using Microsoft.OpenApi.Models;
using Swagger.Filters;
using Swagger;
public class Startup
{
private string _policyCors { get; } = "MyPolicy";
private readonly IConfiguration _configuration;
private readonly IWebHostEnvironment _webHostEnvironment;
public Startup(IConfiguration configuration, IWebHostEnvironment appEnv)
{
_configuration = configuration;
_webHostEnvironment = appEnv;
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services
.ConfigurarControllers()
.ConfigurarCors(_policyCors)
.AdicionarSwagger(_webHostEnvironment)
.AdicionarInjecaoDependencia(_configuration)
.AdicionarHangfire(_configuration, _webHostEnvironment)
//.ConfigurarCompressaoResposta()
.AdicionarAutenticacao(_configuration, _webHostEnvironment)
.AdicionarMailService(_configuration)
.AddSignalR(options => { options.EnableDetailedErrors = true; });
//services.AddSingleton<IUserIdProvider, CustomUserIdProvider>();
services.RegisterDataTables();
if (_webHostEnvironment.IsProduction())
services.AddApplicationInsightsTelemetry();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IRecurringJobManager recurringJobManager, IServiceProvider serviceProvider)
{
app.UseCors(_policyCors);
var scope = serviceProvider.CreateScope();
DomainEvent.Container = new DomainEventsContainer(scope);
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
TelemetryConfiguration.Active.DisableTelemetry = true;
TelemetryDebugWriter.IsTracingDisabled = true;
}
else
{
app.UseHsts();
app.UseHttpsRedirection();
ConfigurarHangfire(app, recurringJobManager, serviceProvider);
}
app.UseStaticFiles();
app.UseRouting();
//app.UseResponseCompression();
//app.UseMiddleware<WebSocketsMiddleware>();
app.UseAuthentication();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<HubNotification>("/hubnotifications");
});
app.UseSwagger(c =>
{
c.RouteTemplate = "docs/{documentname}/swagger.json";
});
app.UseSwaggerUI(c =>
{
c.InjectJavascript("/custom.js");
if (_webHostEnvironment.IsDevelopment())
c.SwaggerEndpoint("/docs/local/swagger.json", "Flow local");
c.SwaggerEndpoint("/docs/v1/swagger.json", "Flow v1");
c.SwaggerEndpoint("/docs/v2/swagger.json", "Flow v2");
c.InjectStylesheet("/custom.css");
c.RoutePrefix = "docs";
c.DisplayRequestDuration();
c.DocExpansion(DocExpansion.List);
c.DefaultModelRendering(ModelRendering.Example);
});
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("pt-BR");
AutomapperConfig.RegisterMappings();
DelegateCompilerConfig.ConfigurarDelegateCompiler();
}
private void ConfigurarHangfire(IApplicationBuilder app, IRecurringJobManager recurringJobManager, IServiceProvider serviceProvider)
{
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
Authorization = new[] { new HangfireAuthorizationFilter() },
});
var azureManagerBackupService = serviceProvider.GetService<IAzureManageBackupService>();
var atualizadorBancos = serviceProvider.GetService<IAtualizadorBancosDeDadosService>();
var timeZoneInfo = TimezoneHelper.ObterTimezoneBrasil();
recurringJobManager.AddOrUpdate(
"RemoverNotificacoesAntigas",
() => atualizadorBancos.RemoverNotificacoesAntigas(30),
"0 1 * * *",
timeZoneInfo
);
recurringJobManager.AddOrUpdate(
"RemoverBackupsAzure",
() => azureManagerBackupService.RemoverPageBlobsAntigos(15),
"0 2 * * *",
timeZoneInfo
);
recurringJobManager.AddOrUpdate(
"DispararEmailEmpresasInativas",
() => atualizadorBancos.DispararEmailDeAvisoEmpresasInativas(7),
"0 3 * * *",
timeZoneInfo
);
recurringJobManager.AddOrUpdate(
"DispararEmailAvisoCursos",
() => atualizadorBancos.DispararEmailDeAvisoCursosFuncionarios(),
"0 6 * * *",
timeZoneInfo
);
}
}
public static class StartupConfiguracoes
{
public static IServiceCollection AdicionarSwagger(this IServiceCollection services, IWebHostEnvironment hostEnvironment)
{
services.AddSwaggerGen(c =>
{
if (hostEnvironment.IsDevelopment())
c.SwaggerDoc("local", new OpenApiInfo() {Title = "Documentação Flow", Version = "local"});
c.SwaggerDoc("v1", new OpenApiInfo() {Title = "Documentação Flow", Version = "v1"});
c.SwaggerDoc("v2", new OpenApiInfo() {Title = "Documentação Flow", Version = "v2"});
c.IgnoreObsoleteActions();
c.IgnoreObsoleteProperties();
c.IncludeXmlComments(FilesComentsSwagger.API);
c.IncludeXmlComments(FilesComentsSwagger.Domain);
c.IncludeXmlComments(FilesComentsSwagger.SharedKernel);
c.OperationFilter<SwaggerImplementationNotesOperationFilter>();
c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme {
In = ParameterLocation.Header,
Description = "Please insert JWT with Bearer into field",
Name = "Authorization",
Type = SecuritySchemeType.ApiKey,
BearerFormat = "Bearer {access_token}",
Scheme = "Bearer"
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement {
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] { }
}
});
c.OperationFilter<AuthResponsesOperationFilter>();
c.DocInclusionPredicate((targetApiVersion, apiDesc) =>
{
return targetApiVersion == "local" || apiDesc.RelativePath.Contains(targetApiVersion);
});
});
return services;
}
public static IServiceCollection ConfigurarControllers(this IServiceCollection services)
{
services.AddControllers().AddNewtonsoftJson(options =>
{
options.SerializerSettings.Formatting = Formatting.None;
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
options.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.None;
options.SerializerSettings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Local;
options.SerializerSettings.Converters = new JsonConverter[] {new ValorCampoPersonalizadoConverter()};
});
services.AddControllers();
return services;
}
public static IServiceCollection ConfigurarCors(this IServiceCollection services, string policy)
{
services.AddCors(o => o.AddPolicy(policy, builder =>
{
builder
.SetIsOriginAllowed(isOriginAllowed: _ => true)
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
.WithExposedHeaders("x-nome-arquivo", "x-notificacoes");
}));
return services;
}
public static IServiceCollection AdicionarHangfire(this IServiceCollection services, IConfiguration iconfiguration, IWebHostEnvironment hostEnvironment)
{
if (hostEnvironment.IsProduction())
{
services.AddHangfire(config => config
.SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UseSqlServerStorage(iconfiguration.GetConnectionString("HangfireConnection"), new SqlServerStorageOptions
{
CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
QueuePollInterval = TimeSpan.Zero,
UseRecommendedIsolationLevel = true,
DisableGlobalLocks = true,
}));
}
else
{
services.AddHangfire(c =>
c.SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
.UseMemoryStorage());
}
services.AddHangfireServer();
return services;
}
public static IServiceCollection ConfigurarCompressaoResposta(this IServiceCollection services)
{
services.Configure<GzipCompressionProviderOptions>(x =>
{
x.Level = CompressionLevel.Fastest;
});
services.AddResponseCompression(x =>
{
x.Providers.Add<GzipCompressionProvider>();
});
services.AddResponseCaching();
return services;
}
public static IServiceCollection AdicionarAutenticacao(this IServiceCollection services, IConfiguration config, IWebHostEnvironment env)
{
var apiJwtToken = new JwtTokenConfig();
config.GetSection(nameof(JwtTokenConfig)).Bind(apiJwtToken);
services.AddSingleton(apiJwtToken);
services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x =>
{
x.TokenValidationParameters = apiJwtToken.TokenValidationParameters;
x.RequireHttpsMetadata = !env.IsDevelopment();
});
return services;
}
public static IServiceCollection AdicionarMailService(this IServiceCollection services, IConfiguration config)
{
var mailConfig = new MailConfig();
config.GetSection(nameof(MailConfig)).Bind(mailConfig);
services.AddSingleton(mailConfig);
services.AddSingleton<MailMessageSender>();
return services;
}
}
}
How I am sending the message: broadcast message
And how I am listening in Angular:
const options = {
accessTokenFactory: () => this.sharedService.token
};
let connection = new signalR.HubConnectionBuilder()
//.withUrl(environment.serviceBaseSignalR + "hubnotifications", options)
.withUrl("http://localhost:25580/hubnotifications", options)
.configureLogging(signalR.LogLevel.Information)
.build();
console.log(connection);
connection.on('broadcastMessage', function (name, message) {
console.log('message received:');
console.log(name);
console.log(message);
});
connection.connection.start().then(function () {
console.log('Now connected, connection ID=' + connection.id)
console.log(connection);
}).catch(function (err) {
return console.error(err.toString());
});

How to create DbContext

below default code in controller working fine
public ProductController(appDbContext parmContext)
{
_context = parmContext;
}
now I want to add DAL and in that, getting error creating object of type appDbContext, what to pass/set for parmContext?
below is the connection in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddDbContext<appDbContext>(config =>
{
config.UseMySql(Configuration.GetConnectionString("PreSales"));
});
}
Below is the code I want to use
public IEnumerable<ProductStatusMaster> GetProductStatusFRdal()
// here I ant to create object of DBcontext (i.e. _context)
{
try
{
var msm = _context.ProductStatusMaster
.Where(s => s.ActiveYn == 1 )
.OrderBy(s => s.Status)
.ToList();
return msm;
}
catch
{
throw;
}
}
Let me get an answer.
There are 2 ways to realize code you want.
1) Through Controller DI.
2) Through Service Locator pattern (antipattern).
By the code:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddDbContext<appDbContext>(config =>
{
config.UseMySql(Configuration.GetConnectionString("PreSales"));
});
}
you set up dependency for DbContext and configure connection parameters. So, for the first method you should pass the dependency through controller. Possible code will be like this:
public class MyTestAppController
{
private appDbContext _context;
public MyTestApController(appDbContext externalAppDbContext)
{
_context = externalAppDbContext;
}
public IEnumerable<ProductStatusMaster> GetProductStatusFRdal()
{
try
{
var msm = _context.ProductStatusMaster
.Where(s => s.ActiveYn == 1 )
.OrderBy(s => s.Status)
.ToList();
return msm;
}
catch
{
throw;
}
}
}
2) Using Service Locator pattern
In this case you should use IServiceProvider dependency. Code example like this:
public class MyTestAppController
{
private IServiceProvider _provider;
public MyTestAppController(IServiceProvider provider) => _provider = provider;
public IEnumerable<ProductStatusMaster> GetProductStatusFRdal()
{
var _context = _provider.GetService<appDbContext>();
try
{
var msm = _context.ProductStatusMaster .Where(s => s.ActiveYn == 1 )
.OrderBy(s => s.Status) .ToList(); return msm;
}
catch { throw; }
}
}

Returning null on httpContextAccessor.HttpContext

We override SaveChangesAsync() to update automatically for DateCreated, CreatedBy, LastDateModified and LastModifiedBy. With CreatedBy and LastModifiedBt, we need to the User Id of Identity.
In our constructor for ApplicationDbContext, we've added something like this:
_userName = httpContextAccessor.HttpContext.User.Identity.Name;
//_userID = userManager.GetUserId(httpContext.HttpContext.User);
.. and always get the null in this httpContextAccessor.HttpContext. Any ideas? We included the source below.
Environment:
.NET Core 2.1
SQL Server
ApplicationDBContext.cs:
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using AthlosifyWebArchery.Models;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Linq.Expressions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
namespace AthlosifyWebArchery.Data
{
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, string>
{
private readonly string _userID;
private readonly string _userName;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options,
IHttpContextAccessor httpContextAccessor
)
: base(options)
{
_userName = httpContextAccessor.HttpContext.User.Identity.Name;
//_userID = userManager.GetUserId(httpContext.HttpContext.User);
}
public DbSet<AthlosifyWebArchery.Models.TournamentBatchItem> TournamentBatchItem { get; set; }
public DbSet<AthlosifyWebArchery.Models.TournamentBatch> TournamentBatch { get; set; }
public virtual DbSet<AthlosifyWebArchery.Models.Host> Host { get; set; }
public DbSet<AthlosifyWebArchery.Models.HostApplicationUser> HostApplicationUser { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
foreach (var entityType in builder.Model.GetEntityTypes())
{
// 1. Add the IsDeleted property
entityType.GetOrAddProperty("IsDeleted", typeof(bool));
// 2. Create the query filter
var parameter = Expression.Parameter(entityType.ClrType);
// EF.Property<bool>(post, "IsDeleted")
var propertyMethodInfo = typeof(EF).GetMethod("Property").MakeGenericMethod(typeof(bool));
var isDeletedProperty = Expression.Call(propertyMethodInfo, parameter, Expression.Constant("IsDeleted"));
// EF.Property<bool>(post, "IsDeleted") == false
BinaryExpression compareExpression = Expression.MakeBinary(ExpressionType.Equal, isDeletedProperty, Expression.Constant(false));
// post => EF.Property<bool>(post, "IsDeleted") == false
var lambda = Expression.Lambda(compareExpression, parameter);
builder.Entity(entityType.ClrType).HasQueryFilter(lambda);
}
// Many to Many relationship
builder.Entity<HostApplicationUser>()
.HasKey(bc => new { bc.HostID, bc.Id });
builder.Entity<HostApplicationUser>()
.HasOne(bc => bc.Host)
.WithMany(b => b.HostApplicationUsers)
.HasForeignKey(bc => bc.HostID);
builder.Entity<HostApplicationUser>()
.HasOne(bc => bc.ApplicationUser)
.WithMany(c => c.HostApplicationUsers)
.HasForeignKey(bc => bc.Id);
}
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
OnBeforeSaving();
return base.SaveChanges(acceptAllChangesOnSuccess);
}
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken))
{
OnBeforeSaving();
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
private void OnBeforeSaving()
{
// Added
var added = ChangeTracker.Entries().Where(v => v.State == EntityState.Added && typeof(IBaseEntity).IsAssignableFrom(v.Entity.GetType())).ToList();
added.ForEach(entry =>
{
((IBaseEntity)entry.Entity).DateCreated = DateTime.UtcNow;
((IBaseEntity)entry.Entity).CreatedBy = _userID;
((IBaseEntity)entry.Entity).LastDateModified = DateTime.UtcNow;
((IBaseEntity)entry.Entity).LastModifiedBy = _userID;
});
// Modified
var modified = ChangeTracker.Entries().Where(v => v.State == EntityState.Modified &&
typeof(IBaseEntity).IsAssignableFrom(v.Entity.GetType())).ToList();
modified.ForEach(entry =>
{
((IBaseEntity)entry.Entity).LastDateModified = DateTime.UtcNow;
((IBaseEntity)entry.Entity).LastModifiedBy = _userID;
});
// Deleted
//var deleted = ChangeTracker.Entries().Where(v => v.State == EntityState.Deleted &&
//typeof(IBaseEntity).IsAssignableFrom(v.Entity.GetType())).ToList();
var deleted = ChangeTracker.Entries().Where(v => v.State == EntityState.Deleted).ToList();
deleted.ForEach(entry =>
{
((IBaseEntity)entry.Entity).DateDeleted = DateTime.UtcNow;
((IBaseEntity)entry.Entity).DeletedBy = _userID;
});
foreach (var entry in ChangeTracker.Entries()
.Where(e => e.State == EntityState.Deleted &&
e.Metadata.GetProperties().Any(x => x.Name == "IsDeleted")))
{
switch (entry.State)
{
case EntityState.Added:
entry.CurrentValues["IsDeleted"] = false;
break;
case EntityState.Deleted:
entry.State = EntityState.Modified;
entry.CurrentValues["IsDeleted"] = true;
break;
}
}
}
}
}
Startup.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using AthlosifyWebArchery.Data;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using AthlosifyWebArchery.Models;
using DinkToPdf.Contracts;
using DinkToPdf;
namespace AthlosifyWebArchery
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
//services.AddHttpContextAccessor();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton(typeof(IConverter), new SynchronizedConverter(new PdfTools()));
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
// Extended Application User from IdentityUser
// and ApplicationRole from IdentityRole
services.AddIdentity<ApplicationUser, ApplicationRole>(
options => options.Stores.MaxLengthForKeys = 128)
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultUI()
.AddDefaultTokenProviders();
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizeFolder("/Tournaments");
options.Conventions.AuthorizeFolder("/TournamentAtheletes");
options.Conventions.AuthorizeFolder("/TournamentBatches");
options.Conventions.AuthorizeFolder("/TournamentContingents");
options.Conventions.AuthorizeFolder("/Admin");
//options.Conventions.AuthorizeFolder("/Private");
//options.Conventions.AllowAnonymousToPage("/Private/PublicPage");
//options.Conventions.AllowAnonymousToFolder("/Private/PublicPages");
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ApplicationDbContext context,
RoleManager<ApplicationRole> roleManager,
UserManager<ApplicationUser> userManager)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc();
//UserManagerInitialData.Initialize(context, userManager, roleManager).Wait();
}
}
}
HttpContext is only valid during a request. When .NET Core creates an ApplicationDbContext class for the call to Configure there is no valid context.
You need to store a reference to the IHttpContextAccessor in your DbContext constructor and then you can use that variable to access the HttpContext property in your OnBeforeSaving() method.
For example:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, string>
{
private readonly IHttpContextAccessor _httpContextAccessor;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options,
IHttpContextAccessor httpContextAccessor
)
: base(options)
{
_httpContextAccessor = httpContextAccessor;
}
....
}
Then, in your OnBeforeSaving() method:
private void OnBeforeSaving()
{
var userName = _httpContextAccessor.HttpContext.User.Identity.Name;
...
}
Think of HttpContext as a telephone call. If you pick the phone up when no-one has called then there is no context i.e. it is null. When someone does call then you have a valid context. This is the same principal for a web call. The Configure method in Startup is not a web call and, as such, does not have a HttpContext.
From another site:
HttpContext object will hold information about the current http
request. In detail, HttpContext object will be constructed newly for
every request given to an ASP.Net application and this object will
hold current request specific informations like Request, Response,
Server, Session, Cache, User and etc. For every request, a new
HttpContext object will be created which the ASP.Net runtime will use
during the request processing. A new HttpContext object will be
created at the beginning of a request and destroyed when the request
is completed.
Above answer explain it well but i would like to highlight another scenario where it could be null as well. For ex:
public class SomeClass
{
SomeClass(IHttpContextAccessor accessor) {}
IActionResult SomeMethod()
{
_ = Task.Run(() =>
{
// use accessorHere
}
return Ok();
}
}
There is a chance that Api call is returned before Thread can access the IHttpContextAccessor and there is a chance that IHttpConextAccessor.HttpContext could be null.
So it is better if we can fetch the required values from the HttpContext for ex: userclaims and pass them as seperate object to the required function.