Cannot Create A DbSet for ApplicationUser - entity-framework

I have an asp.net core project that I am refactoring. Previously I had all my database logic contained within the project, however as we are now adding a WebAPI, I have moved the database logic to a separate .net core standard project, so it is shared between the two projects.
This seems to work fine in the new web api, however I am having issues in the original project, relating to signInManager and the ApplicationUser class.
All compiles just fine, however, I get the following error during runtime:
InvalidOperationException: Cannot create a DbSet for 'ApplicationUser' because this type is not included in the model for the context.`
I have also moved this ApplicationUser class to the new DAL project, and as far as I can see, i've updated all references to it (certainly enough to pass the compile time checks).
My startup.cs is as follows:
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.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// password policy settings
services.Configure<IdentityOptions>(options =>
{
// Password settings
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = false;
options.Password.RequiredUniqueChars = 6;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
options.Lockout.AllowedForNewUsers = true;
// User settings
options.User.RequireUniqueEmail = true;
});
// Add session cookie
services.ConfigureApplicationCookie(options =>
{
// Cookie settings
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
// If the LoginPath isn't set, ASP.NET Core defaults
// the path to /Account/Login.
options.LoginPath = "/Account/Login";
// If the AccessDeniedPath isn't set, ASP.NET Core defaults
// the path to /Account/AccessDenied.
options.AccessDeniedPath = "/Account/AccessDenied";
options.SlidingExpiration = true;
// Disable redirect to login page for unauthorized requests to / api resources
options.Events.OnRedirectToLogin = context =>
{
if (context.Request.Path.StartsWithSegments("/api") && context.Response.StatusCode == StatusCodes.Status200OK)
{
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
return Task.FromResult<object>(null);
}
else
{
context.Response.Redirect(context.RedirectUri);
return Task.FromResult<object>(null);
}
};
});
// Add application services.
services.AddTransient<IEmailSender, EmailSender>();
// add our Db handlers
services.AddScoped<DAL.Services.Interfaces.IServices, DAL.Services.Services>();
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider services)
{
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
var cultureInfo = new CultureInfo("en-GB");
CultureInfo.DefaultThreadCurrentCulture = cultureInfo;
CultureInfo.DefaultThreadCurrentUICulture = cultureInfo;
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
I have a "using" statement at the top as follows:
using IceBowl.DAL;
using IceBowl.DAL.Models;
So the call to "AddIdentity" is passing in the right ApplicationUser - in fact, there is only one ApplicationUser class, I deleted the original.
The code seems to be having issues on the following line:
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
There's something it doesnt like about sign in manager, but I'm at a loss to explain what. All references have been updated and now point to the DAL that contains the datacontext and ApplicationUser class.
Any pointers?

Related

Where ConfigureServices is gone in in ASP.NET Core 5 MVC

I created new ASP.NET Core 5 MVC application from a template with individual accounts, there is no Startup class nor ConfigureServices method. Where are they gone? I have just a static Main.
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
}
}
The templates have changed. You can configure all your services using builder.Services . The changes are documented in this article

How to know the user edited?

I am using Blazor server-side with ASP.NET Core 5 and EF Core 5. I would like that when a record is updated than the ModifiedBy and CreatedBy are generated automatically.
I have overriden SaveChangeAsync as follows:
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
ApplyInterfaces();
return base.SaveChangesAsync(cancellationToken);
}
private void ApplyInterfaces()
{
var userId = _httpContextAccessor?.HttpContext?.User?.Identity?.Name;
var currentUsername = !string.IsNullOrEmpty(userId)? userId: "Anonymous";
foreach (ICreatedBy entity in ChangeTracker.Entries().Where(x => x.Entity is ICreatedBy).Where(x => x.State == EntityState.Added).Select(x=> x.Entity))
{
entity.CreatedBy = currentUsername;
entity.CreatedDate = DateTime.Now;
}
foreach (IModifiedBy entity in ChangeTracker.Entries().Where(x=> x.State == EntityState.Modified).Where(x => x.Entity is IModifiedBy).Select(x => x.Entity))
{
entity.ModifiedBy = currentUsername;
entity.ModifiedDate = DateTime.Now;
}
}
_httpContextAccessor is injected to the DBContext. I added the services
services.AddHttpContextAccessor();
Everything works fine on IIS Express. But when I publish to IIS _httpContextAccessor.HttpContext is null.
Where did I miss something?
You should not use IHttpContextAccessor in Blazor apps (source).
It's not clear from your code sample if this code is in a Blazor component/page or in a database access library. If it's in the database layer you should modify SaveChangesAsync to add a string username parameter, which you can then pass to ApplyInterfaces.
Getting the user should be the responsibility of the Blazor page/component, using either AuthenticationStateProvider or an AuthorizeView component.
#page "/example"
#inject AuthenticationStateProvider auth;
<button #OnClick="Save">Save</button>
#code
{
async Task Save()
{
var state = await auth.GetAuthenticationStateAsync();
var username = state.User.Identity?.Name ?? "[anon]";
await someObject.SaveChangesAsync(username);
}
}

EF Core 5.x + OData 7.5.6 - $Top doesn't insert TOP inside of EF Query

I'm thinking I missed something very simply but here is what I'm trying to todo. I have a .NET Core 5 project with EF Core 5 + OData 7.5.6. Everything appears to be working except for INSERT a TOP command in the generated SQL Query. Here is my controller. Very simple.
[EnableQuery]
[ApiController]
[Route("odata/[controller]")]
public class ConferenceHistoryController : ControllerBase
{
private readonly cdr_database_2Context _db;
public ConferenceHistoryController(cdr_database_2Context db)
{
_db = db;
}
[HttpGet]
public IEnumerable<_000701CallDataRecord> GetConferenceList()
{
// Return Full List
var query = _db._000701CallDataRecords;
var qs = query.ToQueryString();
return query.ToList();
}
}
When I send in my request to:
https://localhost:44355/odata/ConferenceHistory/?$select=RecordId,version&$top=5
The resulting SQL query is:
SELECT [0].[endDateTime], [0].[id], [0].[organizer], [0].[ParticipantCount], [0].[participants], [0].[PoorCall], [0].[type], [0].[version]
FROM [000701_CallDataRecord] AS [0]
As you can see, it's missing both the TOP and the SELECT commands. I have the following in my Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Add CORS to Project
services.AddCors(options =>
{
options.AddDefaultPolicy(builder => builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
});
// Add OData
services.AddOData();
services.AddControllers();
services.AddDbContext<cdr_database_2Context>(options =>
options.UseSqlServer(Configuration.GetConnectionString("CDRConnection"))
);
// Add Swagger Support
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "ODataAPI", Version = "v1" });
});
SetOutputFormatters(services);
}
And
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// Use HTTPS & Routing
app.UseHttpsRedirection();
app.UseRouting();
// Auth???
app.UseAuthorization();
// Swagger Configure
app.UseSwagger();
app.UseSwaggerUI((config) =>
{
config.SwaggerEndpoint("/swagger/v1/swagger.json", "Swagger Odata Demo Api");
});
// Setup Endpoint for EDM
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.EnableDependencyInjection();
endpoints.Select().Expand().Filter().OrderBy().MaxTop(50).Count();
endpoints.MapODataRoute("odata", "odata", GetEdmModel());
});
}
And
private IEdmModel GetEdmModel()
{
// Add OData - EDM Definitions Below
var odataBuilder = new ODataConventionModelBuilder();
odataBuilder.EntitySet<WeatherForecast>("WeatherForecast");
odataBuilder.EntitySet<_000701CallDataRecord>("ConferenceHistory");
return odataBuilder.GetEdmModel();
}
Just looking for some direction on where I could have gone wrong. Everything else seems to be working really well.
Your controller needs to derive from ODataController not ControllerBase
You need to decorate GetConferenceList() with [Queryable]
OK, I figured out where the issue was in my above. It was with using the .ToList and the IEumerable. So if I changed this:
[HttpGet]
public IEnumerable<_000701CallDataRecord> GetConferenceList()
{
// Return Full List
var query = _db._000701CallDataRecords;
var qs = query.ToQueryString();
return query.ToList();
}
To:
[HttpGet]
public IEnumerable<_000701CallDataRecord> GetConferenceList()
{
// Return Full List
var query = _db._000701CallDataRecords;
var qs = query.ToQueryString();
return query;
}
It works. It's also worth noting that the .ToQueryString() doesn't show the OData/EF insertion of the TOP command. But if I enable .EnableSensitiveDataLogging() in the Startup.cs file, then I clearly see it being inserted into it.
Apparently, calling .ToList() will cause it to process and ignore any of the fancy OData commands. That was the only change and all was good then.

Why am I getting error: "Cannot access disposed object" in .net core 2 with EF and AutoFac?

First the error:
Cannot access a disposed object. A common cause of this error is disposing a context 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, or wrapping the context in a using statement. If you are
using dependency injection, you should let the dependency injection
container take care of disposing context instances.
Object name: 'MemberContext'.
I have 3 projects, Domain, API and WebSPA app.
Domain has 2 modules, DomainModule and MediatorModule
public class DomainModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(typeof(MemberContext).Assembly)
.AsImplementedInterfaces()
.InstancePerLifetimeScope(); // via assembly scan
builder.RegisterType<MemberContext>().AsSelf()
.InstancePerLifetimeScope(); // or individually
}
}
public class MediatorModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
// enables contravariant Resolve() for interfaces with single contravariant ("in") arg
builder
.RegisterSource(new ContravariantRegistrationSource());
// mediator itself
builder
.RegisterType<Mediator>()
.As<IMediator>()
.InstancePerLifetimeScope();
// request handlers
builder
.Register<SingleInstanceFactory>(ctx =>
{
var c = ctx.Resolve<IComponentContext>();
return t =>
{
object o;
return c.TryResolve(t, out o) ? o : null;
};
})
.InstancePerLifetimeScope();
// notification handlers
builder
.Register<MultiInstanceFactory>(ctx =>
{
var c = ctx.Resolve<IComponentContext>();
return t => (IEnumerable<object>) c.Resolve(typeof(IEnumerable<>).MakeGenericType(t));
})
.InstancePerLifetimeScope();
}
}
In API project I have also 2 modules, ApplicationModule and again MediatorModule same as the one above.
public class ApplicationModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(typeof(Startup).Assembly)
.AsImplementedInterfaces()
.InstancePerLifetimeScope(); // via assembly scan
builder.RegisterType<MemberContext>().AsSelf().InstancePerLifetimeScope(); // or individually
}
}
No, when I debug I can see that member context gets newed up on each request, yet on second request, it throws above error. To make sure I am not going crazy, I modified constructor of dbcontext to create an id for context so i can verify they are different. What am I doing wrong?
public MemberContext(DbContextOptions<MemberContext> options) : base(options)
{
MemberContextId = Guid.NewGuid();
Console.WriteLine("member context created: " + MemberContextId);
}
Here is the startup in API
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 IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
// .AllowCredentials()
);
});
services.AddMvc()
.AddControllersAsServices();//Injecting Controllers themselves thru DI
//For further info see: http://docs.autofac.org/en/latest/integration/aspnetcore.html#controllers-as-services
AddSwaggerGen(services);
//var connection = Configuration["ConnectionString"];
//services.AddDbContext<MemberContext>(options => options.UseSqlServer(connection),ServiceLifetime.Scoped);
services.AddEntityFrameworkSqlServer()
.AddDbContext<MemberContext>(options =>
{
options.UseSqlServer(Configuration["ConnectionString"]
//,sqlServerOptionsAction: sqlOptions =>
//{
// sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name);
// sqlOptions.EnableRetryOnFailure(maxRetryCount: 10, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
//}
);
},
ServiceLifetime.Scoped //Showing explicitly that the DbContext is shared across the HTTP request scope (graph of objects started in the HTTP request)
);
var container = new ContainerBuilder();
container.Populate(services);
container.RegisterAssemblyModules(typeof(VIN.Members.Domain.Entities.Member).Assembly,
typeof(Startup).Assembly);
return new AutofacServiceProvider(container.Build());
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
//NOTE: must be before UseMVC !!!
app.UseCors("CorsPolicy");
app.UseMvc();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
}
private void AddSwaggerGen(IServiceCollection services)
{
services.AddSwaggerGen(options =>
{
options.DescribeAllEnumsAsStrings();
options.SwaggerDoc("v1", new Swashbuckle.AspNetCore.Swagger.Info
{
Title = "VIN Members HTTP API",
Version = "v1",
Description = "Members Service HTTP API",
TermsOfService = "Terms Of Service"
});
});
}
}
UPDATE:
What I am trying to do is delete a record. On client side code looks like this
onDelete(item: IMember) {
//TODO: replace this with dialog service component
if (window.confirm('Are sure you want to delete this member?')) {
//put your delete method logic here
this.service.deleteMember(item).subscribe(x => {
this.getMembers();
});
}
}
this delete request gets mapped to a controller that passes it to mediator
Controller
// DELETE api/members/5
[HttpDelete("{id}")]
public void Delete(Guid id)
{
var command = new DeleteMember.Command(id);
_mediator.Send(command).ConfigureAwait(false);
}
and finally handler
public class DeleteMember
{
public class Command : IRequest
{
public Command(Guid memberId)
{
Guard.NotNull(memberId, nameof(memberId));
MemberId = memberId;
}
public Guid MemberId { get; }
}
public class Handler : AsyncRequestHandler<Command>
{
private MemberContext _context;
public Handler(MemberContext context)
{
_context = context;
Console.WriteLine("Delete member context: " + context.MemberContextId);
}
protected override async Task HandleCore(Command cmd)
{
try
{
var member = await _context.FindAsync<Member>(cmd.MemberId);//.ConfigureAwait(false);
// if (member != null)
//// {
_context.Remove(member);
await _context.SaveChangesAsync().ConfigureAwait(false);
// }
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
}
}
As you can see there is no code that disposes that context. Scratching my head.
See this commented out check for member if null. That was throwing error as well, I commented it out just to see what will happen, and now it throws as SaveChangesAsync.
As request completes, context gets disposed. Since command handler uses SaveChangesAsync(), context is disposed before save completes. Culprit is controller method :). It should be async as well.
[HttpDelete("{id}")]
public async Task Delete(Guid id)
{
var command = new DeleteMember.Command(id);
await _mediator.Send(command).ConfigureAwait(false);
}
Your DbContext is scoped, meaning that Dependency Injection will return the same DbContext object every time one is asked for within the same HTTP request (in the case of ASP.NET).
That means that you should not be calling Dispose on your DbContext (otherwise that same object can't be used a second time). That seems to be what is happening to you, intentionally or not.
That does mean you should not be using using with it. Are you using using anywhere in your code against your DbContext?
I don't think you showed the line where the Exception is being thrown.
Update:
Try overriding Dispose in your MemberContext class. Something like this:
public override void Dispose() {
base.Dispose();
}
But just set a breakpoint there. When it breaks (if it does) check the stack trace and see what called it.
This can also be caused by having async void instead of async Task within WebAPI in my experience.

Can't get .net core MVC to redirect 401 to /Account/Login

When I request a controller action that is [Authorize] decorated instead of being redirected to the login page I receive a 401 error.
This is a .net core mvc app using the identity template running on IIS express.
When i run the app from program.cs the redirect to login works fine.
I've added explicit directions to for the cookie authentication to use the /Account/Login redirect both for configuration and services section, as well as configuring Identity to perform this redirect.
I can't get it to work. Below is my StartUp class, what should I change to make it work in IIS express?:
public class Startup
{
private MapperConfiguration _mapperConfiguration { get; set; }
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
if (env.IsDevelopment())
{
// For more details on using the user secret store see http://go.microsoft.com/fwlink/?LinkID=532709
builder.AddUserSecrets();
}
builder.AddEnvironmentVariables();
Configuration = builder.Build();
_mapperConfiguration = new MapperConfiguration(cfg =>
{
cfg.AddProfile(new AutoMapperProfileConfiguration());
});
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>(
option => {
option.Cookies.ApplicationCookie.LoginPath = "/Account/Login";
option.Cookies.ApplicationCookie.AutomaticChallenge = true;
option.Cookies.ApplicationCookie.AutomaticAuthenticate = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddDataProtection();
services.AddMvc();
services.AddSignalR();
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
services.Configure<AuthMessageSenderOptions>(Configuration);
services.Configure<IISOptions>(options => options.AutomaticAuthentication = true);
services.AddSingleton<IMapper>(sp => _mapperConfiguration.CreateMapper());
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, ApplicationDbContext context, RoleManager<IdentityRole> roleManager)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseStaticFiles();
app.UseIdentity();
// Add external authentication middleware below. To configure them please see http://go.microsoft.com/fwlink/?LinkID=532715
//app.UseStatusCodePagesWithReExecute("/Home/Error/{0}");
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "MyCookies",
SlidingExpiration = true,
AutomaticAuthenticate = true,
AutomaticChallenge = true,
LoginPath = new PathString("/Account/Login")
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "index",
template: "{controller=Home}/{id?}",
defaults: new { action = "Index" });
});
app.UseSignalR();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
MyDbInit.Init(context, roleManager);
}
}
I had this same problem all night and could not find a solution to it. Running the site directly from Kestrel redirected fine, but through IIS or IIS Express it simply would not redirect - it would go to a white page.
After posting to the Identity Git about it, I realized that my template was set up to run under 1.0.1 of the framework, not 1.1.0. I updated it to use 1.1.0 and updated all the Nuget packages to 1.1.0 and now it is redirecting in IIS and IIS Express properly.
I'm not sure if the package updates "fixed" something that was screwy, or if this was simply a problem with 1.0.1 that was fixed in 1.1.0.
https://blogs.msdn.microsoft.com/webdev/2016/11/16/announcing-asp-net-core-1-1/
Identity adds cookie authentication automatically. You're adding it a second time in Configure.
When you add the second instance you're setting both the automatic properties, so now two middlewares are trying to do the redirection, and that behaviour is "undefined" (where undefined == "Going to seriously mess things up").
This line inside Configure method in Startup class, resolve me problem:
public class Startup
{
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
app.UseAuthentication(); // <= This line
app.UseMvc(routes =>
{
...
});
}
}