How can I access MongoDb in Asp.Net Core with a Username and Password? - mongodb

I have tried connecting to the database following several different tutorials. Each has had their own method for connecting to MongoDb but none of them have shown me how to connect using a username and password. Here is what I am dealing with:
Startup.cs file:
namespace ShortenUrl
{
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.AddMvc();
services.AddSingleton<MongoConfig>(Configuration.GetSection("mongo").Get<MongoConfig>()); // Similar To: Configuration.GetSection("MongoConfig:Server").Value;
services.AddSingleton<MongoConnector>(); // options.Database =
services.AddSingleton<Database>(); // Cofiguration.GetSection("MongoConfig:Database").Value;
services.AddTransient<UsersRepository>();
}
// 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();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
namespace ShortenUrl.Services.Mongo
{
public class MongoConnector
{
public MongoConnector(MongoConfig config)
{
Client = new MongoClient(new MongoClientSettings
{
Server = MongoServerAddress.Parse(config.Server),
Credential = MongoCredential.CreateCredential(config.Creds.Db, config.Creds.User, config.Creds.Password),
UseSsl = true,
VerifySslCertificate = false,
SslSettings = new SslSettings
{
CheckCertificateRevocation = false
}
});
Database = Client.GetDatabase(config.Database);
}
public IMongoClient Client { get; }
public IMongoDatabase Database { get; set; }
}
}
And appsettings.json:
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
},
"mongo": {
"server": "****************",
"database": "**********",
"creds": {
"db": "**********",
"user": "**********",
"password": "**********"
}
}
This is my Controller with a post method for adding the user permissions and gain access to the database:
public class UsersController : Controller
{
private readonly UsersRepository _repo;
public UsersController(UsersRepository repo)
{
_repo = repo;
}
[HttpPost]
public async Task<IActionResult> Post ([FromBody] string user)
{
await _repo.CreateAsync(user);
return new OkObjectResult(user);
}
}
}
And this is the repository:
public class UsersRepository
{
private readonly Database _db;
public UsersRepository(Database db)
{
_db = db;
}
public async Task<User> CreateAsync(string username)
{
var user = new User { Username = username };
await _db.Users.InsertOneAsync(user);
return user;
}
Update
Model Config:
namespace ShortenUrl.Services.Configs
{
public class MongoCreds
{
public string Db { get; set; }
public string User { get; set; }
public string Password { get; set; }
}
public class MongoConfig
{
public string Server { get; set; }
public string Database { get; set; }
public MongoCreds Creds { get; set; }
}
}
Connector:
public class MongoConnector
{
public MongoConnector(MongoConfig config)
{
Client = new MongoClient(new MongoClientSettings
{
Server = MongoServerAddress.Parse(config.Server),
Credential = MongoCredential.CreateCredential(config.Creds.Db, config.Creds.User, config.Creds.Password),
UseSsl = true,
VerifySslCertificate = false,
SslSettings = new SslSettings
{
CheckCertificateRevocation = false
}
});
Database = Client.GetDatabase(config.Database);
}
public IMongoClient Client { get; }
public IMongoDatabase Database { get; set; }
}
}

Added the route attribute and now it works.
namespace ShortenUrl.Controllers
{
[Route("api/codes")]
public class ShortUrlsController : Controller
{
private readonly ShortUrlRepository _repo;
//private readonly IShortUrlService _service;
public ShortUrlsController(ShortUrlRepository repo /*IShortUrlService service*/)
{
_repo = repo;
//_service = service;
}
[HttpGet("{id}")]
public async Task<IActionResult> Get(string id)
{
var su = await _repo.GetAsync(id);
if (su == null)
return NotFound();
return Ok(su);
}
[HttpPost]
public async Task<IActionResult> Create([FromBody] ShortUrl su)
{
await _repo.CreateAsync(su);
return Ok(su);
}
}
}
More information on routing to controller actions can be found HERE!

Related

UnitOfWork with Unity DI Not Persisting Context Changes

I am trying to set up UoW with Unity DI using .NET Framework, and everything works until I call the context.SaveChanges(), the ChangeTracker.HasChanges() in the context passed to the UoW shows false, so context changes are not persisting through to the UoW implementation. I have a feeling this is due to my registration of the UoW in the Unity container, as this is my first time setting this up. Does anything stand out as incorrect?
EDIT - I went around the UoW functionality and attempted to just call _context.SaveChanges() directly on my context and it still does not save. So it seems it's something beyond the UoW.
UnityConfig
public static class UnityConfig
{
public static void RegisterComponents()
{
var container = new UnityContainer();
container.RegisterType<AccountController>(new InjectionConstructor());
container.RegisterType<IChildFamilyCareProviderLinkRepository, ChildFamilyCareProviderLinkRepository>();
container.RegisterType<IFamilyCareProviderRepository, FamilyCareProviderRepository>();
container.RegisterType<IFamilyCareProviderContactRepository, FamilyCareProviderContactRepository>();
container.RegisterType<IUnitOfWork, UnitOfWork>();
container.RegisterInstance(AutoMapperConfig.Mapper);
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
}
}
UnitOfWork
public class UnitOfWork : IUnitOfWork
{
private readonly RAINContext _context;
public IFamilyCareProviderRepository FamilyCareProviders { get; private set; }
public IFamilyCareProviderContactRepository FamilyCareProviderContacts { get; private set; }
public IChildFamilyCareProviderLinkRepository ChildFamilyCareProviderLinks { get; private set; }
public UnitOfWork(RAINContext context,
IFamilyCareProviderRepository familyCareProviderRepository,
IFamilyCareProviderContactRepository familyCareProviderContactRepository,
IChildFamilyCareProviderLinkRepository childFamilyCareProviderLinkRepository)
{
this._context = context;
this.FamilyCareProviders = familyCareProviderRepository;
this.FamilyCareProviderContacts = familyCareProviderContactRepository;
this.ChildFamilyCareProviderLinks = childFamilyCareProviderLinkRepository;
}
public bool Save()
{
_context.ChangeTracker.HasChanges(); //THIS SHOWS FALSE
var success = _context.SaveChanges(); //No changes are saved to DB
return success > 0;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool dispose)
{
if (dispose)
{
_context.Dispose();
}
}
}
IUnitOfWork
public interface IUnitOfWork : IDisposable
{
IFamilyCareProviderRepository FamilyCareProviders { get; }
IFamilyCareProviderContactRepository FamilyCareProviderContacts { get; }
IChildFamilyCareProviderLinkRepository ChildFamilyCareProviderLinks { get; }
bool Save();
}
Controller
public class FamilyCareProviderController : Controller
{
private readonly IUnitOfWork _unitOfWork;
private readonly IMapper _mapper;
public FamilyCareProviderController(
IUnitOfWork unitOfWork)
{
this._unitOfWork = unitOfWork;
this._mapper = AutoMapperConfig.Mapper;
}
[HttpPost]
public ActionResult CreateFamilyCareProvider(CreateFamilyCareProvider vm)
{
try
{
var fcp = new FamilyCareProvider()
{
Id = Guid.NewGuid().ToString().ToUpper(),
FamilyCareProviderName = vm.FamilyCareProviderName,
IsActive = true,
CreatedById = UserId,
LastUpdatedById = UserId
};
_unitOfWork.FamilyCareProviders.Insert(fcp);
var fcpc = _mapper.Map<FamilyCareProviderContact>(vm.FamilyCareProviderContact);
_unitOfWork.FamilyCareProviderContacts.Insert(fcpc);
var result = _unitOfWork.Save();
return RedirectToAction(nameof(Index));
}
catch (Exception)
{
ModelState.AddModelError("", "Error saving family care provider contact.");
return View(vm);
}
}
}
Is there something that is obvious that stands out in the implementation of this?

Get current logged in user in DbContext

For audit purposes I'm trying to get the current logged in user in my DbContext. However I'm having some issues with this. A few things to take into account:
In Blazor Server we have to use AddDbContextFactory
IHttpContextAccessor returns no result in deployed website (might be because IHttpContextAccessor is not thread safe?)
I created a custom DbContext that injects AuthenticationStateProvider.
public partial class CustomDbContext : DbContext
{
private AuthenticationStateProvider _authenticationStateProvider;
#region construction
public CustomDbContext ()
{
}
public CustomDbContext (AuthenticationStateProvider stateProvider)
{
_authenticationStateProvider = stateProvider;
}
[ActivatorUtilitiesConstructor]
public CustomDbContext (DbContextOptions<CustomDbContext> options, AuthenticationStateProvider stateProvider) : base(options)
{
_authenticationStateProvider = stateProvider;
}
public CustomDbContext(DbContextOptions<CustomDbContext> options) : base(options)
{
}
#endregion
...
In this DbContext, when overwriting the SaveChanges I get the User and their claims:
var state = await _authenticationStateProvider.GetAuthenticationStateAsync();
var userIdClaim = state.User.Claims.FirstOrDefault(c => c.Type == "userId")?.Value;
userId = userIdClaim != null && !string.IsNullOrEmpty(userIdClaim ) ? userIdClaim : string.Empty;
...
However when I call .CreateDbContext(); on the injected DbContextFactory, I get the following exception:
'Cannot resolve scoped service
'Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider'
from root provider.'
I've found some topics about this, but the suggested solution there is to create a custom DbContextFactory that is scoped. But then you lose the reason why you are using the DbContextFactory, no?
Any ideas on how to solve this?
Thank you
The DBContextFactory is a singleton registered in the root application DI container, while the AuthenticationStateProvider is a scoped service that is registered in the Hub session DI container. You can't access a lower order service from a higher order service.
You need to rethink your design and provide the user information from whatever scoped service is making whatever call to need a DbConbtext.
Additional Information
I'm not sure what your data pipeline looks like so this example uses the Blazor template weather forecast.
First a View Service that components inject and use.
This injects the AuthenticationStateProvider. It gets the current user for each request and passes it to the data pipeline in a request object.
public class WeatherForecastViewService
{
private AuthenticationStateProvider _authenticationStateProvider; // scoped service
private WeatherForecastService _weatherForecastService; //Singleton Service
public WeatherForecastViewService(AuthenticationStateProvider authenticationStateProvider, WeatherForecastService weatherForecastService)
{
_authenticationStateProvider = authenticationStateProvider;
_weatherForecastService = weatherForecastService;
}
public async ValueTask SaveWeatherForecast(WeatherForecast record)
{
var user = await GetCurrentUser();
var request = new RecordRequest<WeatherForecast>(record, user );
await _weatherForecastService.SaveRecord(request);
}
private async ValueTask<ClaimsPrincipal> GetCurrentUser()
{
var state = await _authenticationStateProvider.GetAuthenticationStateAsync();
return state.User ?? new ClaimsPrincipal();
}
}
Here are the request and result objects:
public readonly struct RecordRequest<TRecord>
{
public TRecord Record { get; init; }
public ClaimsPrincipal Identity { get; init; }
public RecordRequest(TRecord record, ClaimsPrincipal identity)
{
this.Record = record;
this.Identity = identity;
}
}
public record RecordResult
{
public bool SuccessState { get; init; }
public string Message { get; init; }
private RecordResult(bool successState, string? message)
{
this.SuccessState = successState;
this.Message = message ?? string.Empty;
}
public static RecordResult Success(string? message = null)
=> new RecordResult(true, message);
public static RecordResult Failure(string message)
=> new RecordResult(false, message);
}
And here's the singleton data service
public class WeatherForecastDataService
{
// This is a singleton
private readonly IDbContextFactory<DbContext> _factory;
public WeatherForecastDataService(IDbContextFactory<DbContext> factory)
=> _factory = factory;
public async ValueTask<RecordResult> SaveRecord(RecordRequest<WeatherForecast> request)
{
if (!request.Identity.IsInRole("SomeRole"))
return RecordResult.Failure("User does not have authority");
// simulates some async DB activity
await Task.Delay(100);
// Get your DbContext from the injected Factory
// using var dbContext = this.factory.CreateDbContext();
// do your db stuff
return RecordResult.Success();
}
}
PS I haven'y actually run this code so there may be some typos!
IHttpContextAccessor returns no result in deployed website (might be because IHttpContextAccessor is not thread safe?)
Nothing to do with whether IHttpContextAccessor is not thread safe... It's simply because the HttpContext object is not available in Blazor Server App, as communication between the client side (browser) and server side is done through the SignalR protocol, not HTTP. But there is a way how to access the HttpContext object before the Blazor App is rendered, as the initial call to the app is always made through HTTP request; that is, when you enter a url into the address bar of your browser and hit the enter button. See here how to do that...
The following code snippet describes how to inject an AuthenticationStateProvider into the ApplicationDbContext object created by default when you select Individual Accounts in Blazor Server App.
Copy and test. It should work...
Data/ApplicationDbContext.cs
public class ApplicationDbContext : IdentityDbContext
{
public DbSet<Employee> Employees { get; set; }
private AuthenticationStateProvider _authenticationStateProvider;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext>
options, AuthenticationStateProvider stateProvider)
: base(options)
{
_authenticationStateProvider = stateProvider;
}
public override async Task<int>
SaveChangesAsync(CancellationToken cancellationToken)
{
var stateProvider = await
_authenticationStateProvider.GetAuthenticationStateAsync();
if (stateProvider.User.Identity.IsAuthenticated)
{
Console.WriteLine("Authenticated User name: " +
stateProvider.User.Identity.Name);
}
// Delegate the saving action to the base class
return await base.SaveChangesAsync(cancellationToken);
}
}
Create an Employee Repository class service:
EmployeeRepository.cs
using <put here the namespace of your app>.Data;
using <put here the namespace of your app>.Models;
using Microsoft.EntityFrameworkCore;
public class EmployeeRepository
{
private readonly ApplicationDbContext ApplicationDbContext;
public EmployeeRepository(ApplicationDbContext
applicationDbContext)
{
ApplicationDbContext = applicationDbContext;
}
public async Task<Employee> CreateEmployee(Employee employee)
{
CancellationTokenSource cancellationTokenSource = new
CancellationTokenSource();
CancellationToken token = cancellationTokenSource.Token;
await ApplicationDbContext.Employees.AddAsync(employee);
await ApplicationDbContext.SaveChangesAsync(token);
return employee;
}
}
Index.razor
#inject EmployeeRepository EmployeeRepository
#using <Put here....>.Models
<button type="button" #onclick="SaveEmployee">Save Employee</button>
#if (emp != null)
{
<div>#emp.ID.ToString()</div>
<div>#emp.FirstName</div>
<div>#emp.LastName</div>
<div>#emp.City</div>
}
#code
{
private Employee emp;
private async Task SaveEmployee()
{
Employee employee = new Employee { FirstName = "Joana", LastName = "Brown", City = "London" };
emp = await EmployeeRepository.CreateEmployee(employee);
}
}
Create model class Employee:
Models/Employee.cs
public class Employee
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
}
Note: To test this code, you'll have to create A Blazor Server App with Individual Accounts, create the database, including the Employees table
Last but not least: Startup
// Created by the default template
//services.AddDbContext<ApplicationDbContext>(options =>
// options.UseSqlServer(
// Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>(options =>
options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddDbContextFactory<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")),
ServiceLifetime.Scoped);
// This is your code...
services.AddScoped<ApplicationDbContext>(p =>
p.GetRequiredService<IDbContextFactory<ApplicationDbContext>>
().CreateDbContext());
services.AddScoped<EmployeeRepository>();
services.AddScoped<AuthenticationStateProvider,
RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>();
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddSingleton<WeatherForecastService>();
UPDATE:
but does that no against the the recommendations of Microsoft? They ae suggesting to always use using
var context = DbFactory.CreateDbContext();
You mean:
using var context = DbFactory.CreateDbContext();
No, it is not against the recommendations of Microsoft. It's another way to instantiate the DbContext. I did it that way in order to stick to this code by you:
services.AddScoped<ApplicationDbContext>(p => p.GetRequiredService<IDbContextFactory<ApplicationDbContext>>().CreateDbContext());
Anyhow, these are the changes you should make in order to reflect "Microsoft's recommendations"
Change:
services.AddScoped<ApplicationDbContext>(p => p.GetRequiredService<IDbContextFactory<ApplicationDbContext>>().CreateDbContext());
To:
services.AddScoped<ApplicationDbContext>();
Change:
private readonly ApplicationDbContext ApplicationDbContext;
public EmployeeRepository(ApplicationDbContext
applicationDbContext)
{
ApplicationDbContext = applicationDbContext;
}
To:
private readonly IDbContextFactory<ApplicationDbContext>
DbFactory;
public EmployeeRepository(IDbContextFactory<ApplicationDbContext>
_DbFactory)
{
DbFactory = _DbFactory;
}
And change:
await ApplicationDbContext.Employees.AddAsync(employee);
await ApplicationDbContext.SaveChangesAsync(token);
To:
await context.Employees.AddAsync(employee);
await context.SaveChangesAsync(token);
Also add:
using var context = DbFactory.CreateDbContext();
at the beginning of the EmployeeRepository.CreateEmployee method
Run and test.
Hope this work...
New Version
Data/ApplicationDbContext.cs
public class ApplicationDbContext : IdentityDbContext
{
public DbSet<Employee> Employees { get; set; }
private AuthenticationStateProvider _authenticationStateProvider;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext>
options, AuthenticationStateProvider stateProvider)
: base(options)
{
_authenticationStateProvider = stateProvider;
}
public override async Task<int>
SaveChangesAsync(CancellationToken cancellationToken)
{
var stateProvider = await
_authenticationStateProvider.GetAuthenticationStateAsync();
if (stateProvider.User.Identity.IsAuthenticated)
{
Console.WriteLine("Authenticated User name: " +
stateProvider.User.Identity.Name);
}
// Delegate the saving action to the base class
return await base.SaveChangesAsync(cancellationToken);
}
}
Create an Employee Repository class service:
EmployeeRepository.cs
using <put here the namespace of your app>.Data;
using <put here the namespace of your app>.Models;
using Microsoft.EntityFrameworkCore;
public class EmployeeRepository
{
private readonly IDbContextFactory<ApplicationDbContext> DbFactory;
public EmployeeRepository(IDbContextFactory<ApplicationDbContext> _DbFactory)
{
DbFactory = _DbFactory;
}
public async Task<Employee> CreateEmployee(Employee
employee)
{
using var context = DbFactory.CreateDbContext();
// CancellationTokenSource provides the token and have authority to cancel the token
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken token = cancellationTokenSource.Token;
await context.Employees.AddAsync(employee);
await context.SaveChangesAsync(token);
return employee;
}
}
Index.razor
#inject EmployeeRepository EmployeeRepository
#using <Put here....>.Models
<button type="button" #onclick="SaveEmployee">Save Employee</button>
#if (emp != null)
{
<div>#emp.ID.ToString()</div>
<div>#emp.FirstName</div>
<div>#emp.LastName</div>
<div>#emp.City</div>
}
#code
{
private Employee emp;
private async Task SaveEmployee()
{
Employee employee = new Employee { FirstName = "Joana", LastName = "Brown", City = "London" };
emp = await EmployeeRepository.CreateEmployee(employee);
}
}
Create model class Employee:
Models/Employee.cs
public class Employee
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
}
Note: To test this code, you'll have to create A Blazor Server App with Individual Accounts, create the database, including the Employees table
Last but not least: Startup
public void ConfigureServices(IServiceCollection services)
{
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddDbContextFactory<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")),
ServiceLifetime.Scoped);
services.AddScoped<ApplicationDbContext>();
services.AddScoped<EmployeeRepository>();
services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>();
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddSingleton<WeatherForecastService>();
}

SQLite-net-pcl throws error when adding data - System.NullReferenceException

I'm trying to follow this tutorial on SQLite-net-pcl: https://github.com/jamesmontemagno/MyCoffeeApp
I have this model:
public class TipoUsuarioModel
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string TipoUsuario { get; set;}
public bool PermiteConsultar { get; set; }
public bool PermiteBuscar { get; set; }
public bool PermiteReducirCantidad { get; set;}
public bool PermiteAumentarCantidad { get; set; }
public bool PermiteAgregarArticulos { get; set; }
}
This is my interface:
public interface IUsuarios
{
Task AgregarUsuario(
string nombre,
string contrasenna,
string imagen,
int idtipoUsuario,
bool administrador,
string tipoUsuario);
Task<IEnumerable<UsuarioModel>> ObtieneUsuarios();
Task<UsuarioModel> ObtieneUsuarios(int id);
}
This is my service:
public class TipoUsuario: ITipoUsuario
{
SQLiteAsyncConnection db;
async Task Init()
{
if (db != null)
return;
// Get an absolute path to the database file
var databasePath = Path.Combine(FileSystem.AppDataDirectory, "PlayOnData.db");
db = new SQLiteAsyncConnection(databasePath);
await db.CreateTableAsync<TipoUsuarioModel>();
await AgregarTipoUsuario("Administrador", true, true, true, true, true);
}
public async Task AgregarTipoUsuario(
string tipoUsuario,
bool permiteConsultar,
bool permiteBuscar,
bool permiteReducirCantidad,
bool permiteAumentarCantidad,
bool permiteAgregarArticulos)
{
await Init();
var usuario = new TipoUsuarioModel
{
TipoUsuario = tipoUsuario,
PermiteConsultar = permiteConsultar,
PermiteBuscar = permiteBuscar,
PermiteReducirCantidad = permiteReducirCantidad,
PermiteAumentarCantidad = permiteAumentarCantidad,
PermiteAgregarArticulos = permiteAgregarArticulos
};
await db.InsertAsync(usuario);
}
public async Task<IEnumerable<TipoUsuarioModel>> ObtieneTipoUsuarios()
{
await Init();
var tipoUsuario = await db.Table<TipoUsuarioModel>().ToListAsync();
return tipoUsuario;
}
public async Task<TipoUsuarioModel> ObtieneTipoUsuarios(int id)
{
await Init();
var tipoUsuario = await db.Table<TipoUsuarioModel>()
.FirstOrDefaultAsync(c => c.Id == id);
return tipoUsuario;
}
}
Now, On my main page, I have this:
public partial class HomePage : ContentPage
{
ITipoUsuario tipoService;
public HomePage ()
{
InitializeComponent ();
}
protected override async void OnAppearing()
{
await tipoService.AgregarTipoUsuario("Administrador", true, true, true, true, true);
base.OnAppearing();
}
}
Every time I run the app and excuse the line to create data, it throws this error message:
System.NullReferenceException
What am I missing? Thanks in advance!

Entity Framework core + OData 8.0.1 CRUD Operations ASP.Net core 5

public class Books
{
public int Id{get;set;}
public string Name{get;set;}
}
public class BookController : ODataController
{
private readonly IBookRepository _bookRepository;
private readonly IMapper _mapper;
public BookController(IBookRepository bookRepository, IMapper mapper)
{
_bookRepository = bookRepository;
_mapper = mapper;
}
[EnableQuery]
[HttpGet]
public ActionResult Get()
{
try
{
IQueryable<BookDto> res = _bookRepository.Books().ProjectTo<BookDto>(_mapper.ConfigurationProvider);
if (res.Count() == 0)
return NotFound();
return Ok(res);
}
catch(Exception)
{
return StatusCode(StatusCodes.Status500InternalServerError, "Unable to get Book");
}
}
[HttpGet("{Id}")]
public async Task<ActionResult<Book>> GetBookById(string Id)
{
var book = await _bookRepository.GetBookById(Id);
if (book == null)
return NotFound();
return book;
}
[HttpPost]
public async Task<ActionResult<Book>> Post([fr]CreateBookDto createBookDto)
{
try
{
if (createBookDto == null)
return BadRequest();
Book book = _mapper.Map<Book>(createBookDto);
var result = await _bookRepository.Book(book);
return CreatedAtAction(nameof(GetBookById), new { id = book.UserId }, result);
}
catch (Exception)
{
return StatusCode(StatusCodes.Status500InternalServerError,"Failed to save Book information");
}
}
}
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)
{
var connectionStr = Configuration.GetConnectionString("ConnectionString");
services.AddControllers();
services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
services.AddControllers().AddOData(opt => opt.AddRouteComponents("api",GetEdModel()).Select().Filter().Count().Expand());
services.AddDbContext<AppDbContext>(options => options.UseMySql(connectionStr,ServerVersion.AutoDetect(connectionStr)));
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Book_api", Version = "v1" });
});
services.AddScoped<IBookRepository, BookRepository>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Book_api v1"));
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
private IEdmModel GetEdModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<User>("User");
builder.EntitySet<BookDto>("Book");
return builder.GetEdmModel();
}
}
Hi Guys. I'm trying to implement OData on my ASP.Net Core 5 API. I can retrieve books using the Get. But I am struggling to do a POST. When I try to use the POST on Postman, the CreateBookDto properties all return null. I tried to add [FromBody] that does not work also. The only time this seems to work is when I decorate the controller with [ApiController] but that in turn affects my GET. I'm not sure what to do anymore.

ApplicationUser within an ActionFilter in Asp.Net Core 2.0?

How can I access the current ApplicationUser (or UserManager) within an ActionFilter in Asp.Net Core 2.0?
I am trying to lock down the entire application until the user accepts the EULA (End User License Agreement), changes their password, and fills out required personal information.
public class ApplicationUser : IdentityUser
{
...
public DateTime? DateEULAAccepted { get; set; }
...
}
Here is the ActionFilter code:
public class ProfileRequiredActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.User.Identity.IsAuthenticated)
{
var CurUser = UserManager<ApplicationUser>.GetUserAsync(filterContext.HttpContext.User);
...
if (CurUser.Result.DateEULAAccepted.ToString() == null)
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = "Account", action = "AgreeToEULA" }));
}
...
}
}
}
I am instantiating the ActionFilter in the Startup > ConfigureServices as follows:
...
services.AddMvc(options =>
{
options.Filters.Add(new ProfileRequiredActionFilter());
});
...
Try adding your filter in ConfigureServices() as follows:
services.AddMvc(options => {
options.Filters.Add<ProfileRequiredActionFilter>();
});
You can then inject your UserManager into the filter as follows:
public class ProfileRequiredActionFilter : IActionFilter
{
private UserManager<ApplicationUser> _userManager;
public ProfileRequiredActionFilter(UserManager<ApplicationUser> userManager)
{
_userManager = userManager
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.User.Identity.IsAuthenticated)
{
var CurUser = _userManager<ApplicationUser>.GetUserAsync(filterContext.HttpContext.User);
...
if (CurUser.Result.DateEULAAccepted.ToString() == null)
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = "Account", action = "AgreeToEULA" }));
}
...
}
}
}