DatabaseContext object is disposed ASP.NET Core - 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();

Related

Authorized API is not working even when passing a JWT bearer token

The loginWithJWT() method works fine, the user login normally and the token is created.
enter image description here
but when I try to access the GetAllCountry() method that is [Authorized] after passing the token . An unauthorized response is fired.
enter image description here
This is the JWT description :
enter image description here
Not sure what I have missed.
here is my start up class :
namespace HotelListing
{
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
.CreateLogger();
}
public void ConfigureServices(IServiceCollection services)
{
// Db conn string
services.AddDbContext<ApplicationDbContext>
(options => options.UseSqlServer(Configuration.GetConnectionString("sqlServerConnection")));
//Identity
services.AddAuthentication();
services.AddIdentity<MyIdentityUser, IdentityRole>(options =>
{
options.User.RequireUniqueEmail = true;
}).AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
//enabling JWT
services.AddAuthentication(option =>
{
option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(opt =>
{
opt.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = (Configuration.GetSection("Jwt")).GetSection("Issuer").Value,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Environment.GetEnvironmentVariable("SecretTokenKeyValue")))
};
});
// Mapper
services.AddAutoMapper(typeof(ApplicationMapper));
//DI interfaces and classes
services.AddScoped<ICountryServices, CountrySevices>();
services.AddScoped<IHotelServices, HotelServices>();
services.AddScoped<IUserAuthenticationManager, UserAuthenticationManager>();
//Swagger config
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "HotelListing", Version = "v1" });
});
//Adding Controllers + JSON self refrencing config
services.AddControllers().AddNewtonsoftJson(opt =>
opt.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "HotelListing v1"));
}
app.UseHttpsRedirection();
app.UseCors("AllowAll");
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
here is my UserAuthenticationManager Class:
namespace HotelListing.Services
{
public class UserAuthenticationManager : IUserAuthenticationManager
{
private MyIdentityUser _user;
private UserManager<MyIdentityUser> _userManager;
private IConfiguration _configuration;
public UserAuthenticationManager(UserManager<MyIdentityUser> userManager, IConfiguration configuration)
{
_userManager = userManager;
_configuration= configuration;
}
public async Task<bool> ValidateUser(LoginUserDto userDto)
{
_user = await _userManager.FindByNameAsync(userDto.Email);
if (_user != null && await _userManager.CheckPasswordAsync(_user, userDto.Password))
return true;
else
return false;
}
public async Task<string> CreateToken()
{
var signingCredentials = GetSigningCredentials();
var claims = await GetClaims();
var tokenOptions = GenerateTokenOptions(signingCredentials, claims);
return new JwtSecurityTokenHandler().WriteToken(tokenOptions);
}
private SigningCredentials GetSigningCredentials()
{
var key = Environment.GetEnvironmentVariable("SecretTokenKeyValue");
var secret = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
return new SigningCredentials(secret, SecurityAlgorithms.HmacSha256);
}
private async Task<List<Claim>> GetClaims()
{
var claimList = new List<Claim>
{
new Claim(ClaimTypes.Name, _user.UserName)
};
var roles = await _userManager.GetRolesAsync(_user);
foreach(var role in roles){
claimList.Add(new Claim(ClaimTypes.Role, role));
}
return claimList;
}
private JwtSecurityToken GenerateTokenOptions(SigningCredentials signingCrenditals, List<Claim> claimList)
{
var jwtSettings = _configuration.GetSection("Jwt");
var token = new JwtSecurityToken(
issuer: jwtSettings.GetSection("Issuer").Value,
claims: claimList,
expires: DateTime.Now.AddMinutes(Convert.ToDouble(jwtSettings.GetSection("lifetime").Value)),
signingCredentials: signingCrenditals);
return token;
}
}
}
here is my UserApiController:
namespace HotelListing.APIs
{
[Route("api/[controller]")]
[ApiController]
public class UserApiController : ControllerBase
{
private UserManager<MyIdentityUser> _userManager { get; }
private IUserAuthenticationManager _userAuthenticationManager { get; }
private IMapper _mapper { get; }
public UserApiController(UserManager<MyIdentityUser> userManager,
IUserAuthenticationManager userAuthenticationManager,
IMapper mapper)
{
_userManager = userManager;
_userAuthenticationManager = userAuthenticationManager;
_mapper = mapper;
}
[HttpPost]
[Route("register")]
public async Task<IActionResult> Register([FromBody] UserDTO userDTO)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
try
{
var user = _mapper.Map<MyIdentityUser>(userDTO);
user.UserName = userDTO.Email;
var result = await _userManager.CreateAsync(user, userDTO.Password);
if (!result.Succeeded)
{
foreach(var error in result.Errors)
{
ModelState.AddModelError(error.Code, error.Description);
}
return BadRequest(ModelState);
}
await _userManager.AddToRolesAsync(user, userDTO.roles);
return Accepted();
}catch(Exception ex)
{
return StatusCode(500, ex.Message + "Internal server error");
}
}
[HttpPost]
[Route("loginWithJWT")]
public async Task<IActionResult> LoginWithJWT([FromBody] LoginUserDto userDTO)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
try
{
var result = await _userAuthenticationManager.ValidateUser(userDTO);
if (result != true)
{
return Unauthorized(userDTO);
}
return Accepted(new { Token = await _userAuthenticationManager.CreateToken()});
}
catch (Exception ex)
{
return Problem($"Something went wrong in {nameof(LoginWithJWT)} error is {ex.Message}", statusCode:500);
}
}
}
}
Iam trying to access the authorized GetAllCountries() method in CountryApi class:
namespace HotelListing.APIs
{
[Route("api/[controller]")]
[ApiController]
public class CountryApiController : ControllerBase
{
private ICountryServices _countryServices { get; }
private IMapper _mapper { get; }
public CountryApiController(ICountryServices countryServices, IMapper mapper)
{
_countryServices = countryServices;
_mapper = mapper;
}
**// GET: api/<CountryApiController>
[HttpGet]
[Authorize]
public async Task<IActionResult> GetAllCountries()
{
try{
var countries = await _countryServices.GetAllCountriesAsync();
return Ok(_mapper.Map<List<CountryDTO>>(countries));
}
catch(Exception ex)
{
return StatusCode(500, ex.Message + "Internal server error");
}
}**
// GET: api/<CountryApiController>/id
[HttpGet("{id:int}")]
public async Task<IActionResult> GetCountryById(int id)
{
try
{
var country = await _countryServices.GetCountryByIdAsync(id);
return Ok(_mapper.Map<CountryDTO>(country));
}
catch (Exception ex)
{
return StatusCode(500, ex.Message + "Internal server error");
}
}
}
}

Value cannot be null - In-Memory Database .Net

I am trying to use a In-Memory Database, but I get this message:
System.ArgumentNullException: 'Value cannot be null. (Parameter
'source')'
I read a lot some similar question related with this issue, but every article is related with the connection String, and I think I must not use a ConnectionString because is a In-Memory Database. What Do I do wrong? I leave my code:
DbInitializer.cs - In this class appears the error
public static class DbInitializer
{
public static void Initialize(IServiceProvider serviceProvider)
{
using (var _context = new AppDBContext(serviceProvider.GetRequiredService<DbContextOptions<AppDBContext>>()))
{
if (_context.AgentsRole.Any()) //In this line appears the error
return;
_context.AgentsRole.AddRange(
new Agent { Id = 1, Role_Name = "David Lopez", Is_Active = true, Role_Description = "This is a test for David Lopez" },
new Agent { Id = 2, Role_Name = "James Norris", Is_Active = false, Role_Description = "This is a test for James Norris" },
new Agent { Id = 3, Role_Name = "Jhon Norris", Is_Active = true, Role_Description = "This is a test for Jhon Norris" },
new Agent { Id = 4, Role_Name = "James Norr", Is_Active = true, Role_Description = "This is a test for James Norr" }
);
_context.SaveChanges();
}
}
}
Startup.cs:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDBContext>(options=> options.UseInMemoryDatabase(databaseName: "InMemory_DB"));
services.AddControllers();
services.AddSwaggerGen();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My Test1 Api v1");
});
}
}
Program.cs:
public class Program
{
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var context = services.GetRequiredService<AppDBContext>();
DbInitializer.Initialize(services);
}
host.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Controller.cs:
[ApiController]
[Route("api/[controller]")]
public class AgentRoleController : ControllerBase
{
private readonly ILogger<AgentRoleController> _logger;
private readonly AppDBContext _context;
public AgentRoleController(ILogger<AgentRoleController> logger, AppDBContext context)
{
_logger = logger;
_context = context;
}
[HttpGet]
[SwaggerOperation("GetAgentsRole")]
[SwaggerResponse((int)HttpStatusCode.OK)]
[SwaggerResponse((int)HttpStatusCode.NotFound)]
public IEnumerable<Agent> Get()
{
return _context.AgentsRole;
}
}
AppDBContext.cs:
public class AppDBContext : DbContext
{
public AppDBContext(DbContextOptions<AppDBContext> options)
:base(options)
{
}
public DbSet<Agent> AgentsRole;
}
The solution is in AppDBContext.cs, I missed the initialization of AgentsRole, the solution is:
public class AppDBContext : DbContext
{
public AppDBContext(DbContextOptions<AppDBContext> options)
:base(options)
{
}
public DbSet<Agent> AgentsRole { get; set; } // I added this Part!
}

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.

Quartz - .NET Core - Null Reference Exception

I'm trying to implement the Quartz for .NET Core.
At the moment I have the following:
public class Startup
{
**private IScheduler _scheduler { get; set; }//quartz**
public Startup(IConfiguration configuration)
{
Configuration = configuration;
**_scheduler = ConfigureQuartz();//quartz**
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<Context>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("notebook14"));
});
...
...
...
**#region for Quartz DI
services.AddScoped<Job>();
services.AddSingleton(provider => _scheduler);
#endregion**
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapRazorPages();
endpoints.MapControllerRoute(
name: "default",
pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
});
}
**#region for Quartz
private IScheduler ConfigureQuartz()
{
NameValueCollection properties = new NameValueCollection()
{
{"quartz.serializer.type","binary"}
};
StdSchedulerFactory factory = new StdSchedulerFactory(properties);
_scheduler = factory.GetScheduler().Result;
_scheduler.Start();
return _scheduler;
}
#endregion**
...and Program.cs because I would like to start a schedule at first deploy and then run the task every day:
public class Program
{
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
UserManager<AppUser> userManager = services.GetRequiredService<UserManager<AppUser>>();
RoleManager<AppRole> roleManager = services.GetRequiredService<RoleManager<AppRole>>();
**IScheduler scheduler = services.GetRequiredService<IScheduler>();
Start.StartSchedule(scheduler);**
Init.AssignAdmin(userManager, roleManager).Wait();
}
host.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
...and Start.cs, where I have created the job and the trigger :
public class Start
{
public static Context _context { get; set; }
public Start(Context context)
{
_context = context;
}
public async static void StartSchedule(IScheduler _scheduler)
{
IJobDetail job = JobBuilder.Create<Job>().WithIdentity("Generate", "FreeSlots").Build();
ITrigger trigger = TriggerBuilder.Create().WithIdentity("Trigger", "FreeSlots").StartNow()
.WithDailyTimeIntervalSchedule(t=>t.StartingDailyAt(new TimeOfDay(10,59)))
.Build();
await _scheduler.ScheduleJob(job, trigger);
}
}
...and finally, the Job itself:
public class Job : IJob
{
public Task Execute(IJobExecutionContext context)
{
try
{
Console.WriteLine("TRIGGERED!");
List<AppUser> doctors = Start._context.Users.Where(x => x.Type == AppUser.UserType.Doctor).ToList();
DateTime First = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 8, 0, 0);
List<Appointments> appointments = new List<Appointments>()
{
new Appointments(){Date=First, Time=First,Status=Appointments.SlotStatus.Free},
new Appointments(){Date=First, Time=First.AddHours(1),Status=Appointments.SlotStatus.Free},
new Appointments(){Date=First, Time=First.AddHours(2),Status=Appointments.SlotStatus.Free},
new Appointments(){Date=First, Time=First.AddHours(3),Status=Appointments.SlotStatus.Free},
new Appointments(){Date=First, Time=First.AddHours(4),Status=Appointments.SlotStatus.Free},
new Appointments(){Date=First, Time=First.AddHours(5),Status=Appointments.SlotStatus.Free},
new Appointments(){Date=First, Time=First.AddHours(6),Status=Appointments.SlotStatus.Free},
new Appointments(){Date=First, Time=First.AddHours(7),Status=Appointments.SlotStatus.Free},
new Appointments(){Date=First, Time=First.AddHours(8),Status=Appointments.SlotStatus.Free},
};
foreach (AppUser doc in doctors)
{
foreach (Appointments slot in appointments)
{
slot.DoctorId = doc.Id;
Start._context.Appointments.Add(slot);
Start._context.SaveChanges();
var message = "Slot for " + slot.DoctorId.ToString() + " " + slot.Time + " " + slot.Status;
Console.WriteLine(message);
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
return Task.CompletedTask;
}
}
The problem is that I get a null reference exception when the Job is executed:
Does anybody have any ideas? Is there a way to make this work? Thank you in advance!

How to make Route based on a sub-domain MVC Core

How to make this
- user1.domain.com goes to user1/index (not inside area)
- user2.domain.com goes to user2/index (not inside area)
I mean's the
user1.domain.com/index
user2.domain.com/index
Are same view but different data depending on user{0}
using MVC Core 2.2
There're several approaches depending on your needs.
How to make this - user1.domain.com goes to user1/index (not inside area) - user2.domain.com goes to user2/index (not inside area)
Rewrite/Redirect
One approach is to rewrite/redirect the url. If you don't like do it with nginx/iis, you could create an Application Level Rewrite Rule. For example, I create a sample route rule for your reference:
internal enum RouteSubDomainBehavior{ Redirect, Rewrite, }
internal class RouteSubDomainRule : IRule
{
private readonly string _domainWithPort;
private readonly RouteSubDomainBehavior _behavior;
public RouteSubDomainRule(string domain, RouteSubDomainBehavior behavior)
{
this._domainWithPort = domain;
this._behavior = behavior;
}
// custom this method according to your needs
protected bool ShouldRewrite(RewriteContext context)
{
var req = context.HttpContext.Request;
// only rewrite the url when it ends with target doamin
if (!req.Host.Value.EndsWith(this._domainWithPort, StringComparison.OrdinalIgnoreCase)) { return false; }
// if already rewrite, skip
if(req.Host.Value.Length == this._domainWithPort.Length) { return false; }
// ... add other condition to make sure only rewrite for the routes you wish, for example, skip the Hub
return true;
}
public void ApplyRule(RewriteContext context)
{
if(!this.ShouldRewrite(context)) {
context.Result = RuleResult.ContinueRules;
return;
}
var req = context.HttpContext.Request;
if(this._behavior == RouteSubDomainBehavior.Redirect){
var newUrl = UriHelper.BuildAbsolute( req.Scheme, new HostString(this._domainWithPort), req.PathBase, req.Path, req.QueryString);
var resp = context.HttpContext.Response;
context.Logger.LogInformation($"redirect {req.Scheme}://{req.Host}{req.Path}?{req.QueryString} to {newUrl}");
resp.StatusCode = 301;
resp.Headers[HeaderNames.Location] = newUrl;
context.Result = RuleResult.EndResponse;
}
else if (this._behavior == RouteSubDomainBehavior.Rewrite)
{
var host = req.Host.Value;
var userStr = req.Host.Value.Substring(0, host.Length - this._domainWithPort.Length - 1);
req.Host= new HostString(this._domainWithPort);
var oldPath = req.Path;
req.Path = $"/{userStr}{oldPath}";
context.Logger.LogInformation($"rewrite {oldPath} as {req.Path}");
context.Result = RuleResult.SkipRemainingRules;
}
else{
throw new Exception($"unknow SubDomainBehavoir={this._behavior}");
}
}
}
(Note I use Rewrite here. If you like, feel free to change it to RouteSubDomainBehavior.Redirect.)
And then invoke the rewriter middleware just after app.UseStaticFiles():
app.UseStaticFiles();
// note : the invocation order matters!
app.UseRewriter(new RewriteOptions().Add(new RouteSubDomainRule("domain.com:5001",RouteSubDomainBehavior.Rewrite)));
app.UseMvc(...)
By this way,
user1.domain.com:5001/ will be rewritten as (or redirected to) domain.com:5001/user1
user1.domain.com:5001/Index will be rewritten as(or redirected to) domain.com:5001/user1/Index
user1.domain.com:5001/Home/Index will be rewritten as (or redirected to) domain.com:5001/user1//HomeIndex
static files like user1.domain.com:5001/lib/jquery/dist/jquery.min.js won't be rewritten/redirected because they're served by UseStaticFiles.
Another Approach Using IModelBinder
Although you can route it by rewritting/redirecting as above, I suspect what your real needs are binding parameters from Request.Host. If that's the case, I would suggest you should use IModelBinder instead. For example, create a new [FromHost] BindingSource:
internal class FromHostAttribute : Attribute, IBindingSourceMetadata
{
public static readonly BindingSource Instance = new BindingSource( "FromHostBindingSource", "From Host Binding Source", true, true);
public BindingSource BindingSource {get{ return FromHostAttribute.Instance; }}
}
public class MyFromHostModelBinder : IModelBinder
{
private readonly string _domainWithPort;
public MyFromHostModelBinder()
{
this._domainWithPort = "domain.com:5001"; // in real project, use by Configuration/Options
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var req = bindingContext.HttpContext.Request;
var host = req.Host.Value;
var name = bindingContext.FieldName;
var userStr = req.Host.Value.Substring(0, host.Length - this._domainWithPort.Length - 1);
if (userStr == null) {
bindingContext.ModelState.AddModelError(name, $"cannot get {name} from Host Domain");
} else {
var result = Convert.ChangeType(userStr, bindingContext.ModelType);
bindingContext.Result = ModelBindingResult.Success(result);
}
return Task.CompletedTask;
}
}
public class FromHostBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null) { throw new ArgumentNullException(nameof(context)); }
var has = context.BindingInfo?.BindingSource == FromHostAttribute.Instance;
if(has){
return new BinderTypeModelBinder(typeof(MyFromHostModelBinder));
}
return null;
}
}
Finally, insert this FromHostBinderProvider in your MVC binder providers.
services.AddMvc(otps =>{
otps.ModelBinderProviders.Insert(0, new FromHostBinderProvider());
});
Now you can get the user1.domain.com automatically by:
public IActionResult Index([FromHost] string username)
{
...
return View(view_model_by_username);
}
public IActionResult Edit([FromHost] string username, string id)
{
...
return View(view_model_by_username);
}
The problem after login the Identity cookie not shared in sub-domain
Here my Code where's wrong !!!
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public static Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder dataProtectionBuilder;
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
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("ConnectionDb")));
services.AddIdentity<ExtendIdentityUser, IdentityRole>(options =>
{
options.Password.RequiredLength = 8;
options.Password.RequireUppercase = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequiredUniqueChars = 0;
options.Password.RequireLowercase = false;
}).AddEntityFrameworkStores<ApplicationDbContext>(); // .AddDefaultTokenProviders();
services.ConfigureApplicationCookie(options => options.CookieManager = new CookieManager());
services.AddHttpContextAccessor();
services.AddScoped<IUnitOfWork, UnitOfWork>();
services.AddScoped<IExtendIdentityUser, ExtendIdentityUserRepository>();
services.AddScoped<IItems, ItemsRepository>();
services.AddMvc(otps =>
{
otps.ModelBinderProviders.Insert(0, new FromHostBinderProvider());
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseAuthentication();
//app.UseHttpsRedirection();
app.UseCookiePolicy();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
And this class to sub-domain like that https://user1.localhost:44390/Home/Index
internal class FromHostAttribute : Attribute, IBindingSourceMetadata
{
public static readonly BindingSource Instance = new BindingSource("FromHostBindingSource", "From Host Binding Source", true, true);
public BindingSource BindingSource { get { return FromHostAttribute.Instance; } }
}
public class MyFromHostModelBinder : IModelBinder
{
private readonly string _domainWithPort;
public MyFromHostModelBinder()
{
this._domainWithPort = "localhost:44390"; // in real project, use by Configuration/Options
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var req = bindingContext.HttpContext.Request;
var host = req.Host.Value;
var name = bindingContext.FieldName;
var userStr = req.Host.Value.Substring(0, host.Length - this._domainWithPort.Length);
if (string.IsNullOrEmpty(userStr))
{
bindingContext.ModelState.AddModelError(name, $"cannot get {name} from Host Domain");
}
else
{
var result = Convert.ChangeType(userStr, bindingContext.ModelType);
bindingContext.Result = ModelBindingResult.Success(result);
}
return Task.CompletedTask;
}
}
public class FromHostBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null) { throw new ArgumentNullException(nameof(context)); }
var has = context.BindingInfo?.BindingSource == FromHostAttribute.Instance;
if (has)
{
return new BinderTypeModelBinder(typeof(MyFromHostModelBinder));
}
return null;
}
}
Using ICookieManager
public class CookieManager : ICookieManager
{
#region Private Members
private readonly ICookieManager ConcreteManager;
#endregion
#region Prvate Methods
private string RemoveSubdomain(string host)
{
var splitHostname = host.Split('.');
//if not localhost
if (splitHostname.Length > 1)
{
return string.Join(".", splitHostname.Skip(1));
}
else
{
return host;
}
}
#endregion
#region Public Methods
public CookieManager()
{
ConcreteManager = new ChunkingCookieManager();
}
public void AppendResponseCookie(HttpContext context, string key, string value, CookieOptions options)
{
options.Domain = RemoveSubdomain(context.Request.Host.Host); //Set the Cookie Domain using the request from host
ConcreteManager.AppendResponseCookie(context, key, value, options);
}
public void DeleteCookie(HttpContext context, string key, CookieOptions options)
{
ConcreteManager.DeleteCookie(context, key, options);
}
public string GetRequestCookie(HttpContext context, string key)
{
return ConcreteManager.GetRequestCookie(context, key);
}
#endregion
}