How to convert ODataQueryOptions<DtoType> to ODataQueryOptions<EntityType> to query the underlying storage? - asp.net-core

I have a webapi controller using ProductDTO type for clients but the repository is using a Product type.
I would like to use odata on my endpoint. I receive the ODataQueryOptions parameter and I want to pass it to repository (implemented using CosmosDB).
I cant seem to figure out how to convert from ODataQueryOptions<ProductDTO> to ODataQueryOptions<Product>.
[Route("api/[controller]")]
public class ProductsController<ProductsDTO, Product> : ControllerBase
{
IRepository<Product> _repository;
IMapper _mapper;
[HttpGet]
public async Task<ActionResult<IList<ProductDTO>>> Get(ODataQueryOptions<ProductDTO> queryOptions)
{
var mappedQueryOptions = ... // convert 'queryOptions' to ODataQueryOptions<Product> ???
var products = await _repository.Get(mappedQueryOptions);
return Ok(_mapper.Map<IEnumerable<Product>, IEnumerable<ProductDTO>>(products));
}
}
In my aspnetcore service composition I create and inject automapper
var configuration = new MapperConfiguration(cfg =>
{
cfg.AddProfile(new ProductProfile());
cfg.AddExpressionMapping();
});
internal class ProductProfile : Profile
{
public ProductProfile()
{
CreateMap<Product, ProductDto>().ReverseMap();
}
}
I managed to extract the queryoptions filter as a lambda expression Expression<Func<Product, bool>> (using automapper MapExpression) and passed it to repository , that works to a certain extent but I want to get the select , top, skip, etc. as well.
Any suggestions on how that could be done?

can you review the example?
[ODataRouteComponent("api")]
public class UsersController : ODataController
{
#region ctor
private readonly ILogger<UsersController> _logger;
private readonly IMapper _mapper;
private readonly UserManager<AspNetUser> _userManager;
public UsersController(IMapper mapper,
UserManager<AspNetUser> userManager,
ILogger<UsersController> logger)
{
_mapper = mapper;
_userManager = userManager;
_logger = logger;
}
#endregion
[HttpGet]
[EnableQuery(PageSize = 10)]
public async Task<IActionResult> Get(ODataQueryOptions<UserDto> options)
{
var user = await _userManager.GetUserAsync(User);
_logger.LogInformation("{UserUserName} is getting all users", user.UserName);
return Ok(await _userManager.Users.GetAsync(_mapper, options));
}
[HttpGet]
[EnableQuery]
public async Task<IActionResult> Get(int key, ODataQueryOptions<UserDto> options)
{
var user = await _userManager.GetUserAsync(User);
_logger.LogInformation("{UserUserName} is getting user with id {Key}", user.UserName, key.ToString());
return Ok(_userManager.Users.Get(_mapper, options).FirstOrDefault(x => x.Id == key));
}
}
services.AddControllers(opt =>{}).AddOData((opt, _) =>
{
opt.EnableQueryFeatures().AddRouteComponents("api", EdmModelBuilder.GetEdmModelv1());
});
public class MappingProfile : Profile
{
public MappingProfile() : base(nameof(WebUI))
{
CreateMap<AspNetUser, UserDto>();
}
}
internal static class EdmModelBuilder
{
internal static IEdmModel GetEdmModelv1()
{
ODataConventionModelBuilder builder = new();
builder.EnableLowerCamelCase();
#region UserDto
builder.EntitySet<UserDto>(nameof(DataListContext.Users))
.EntityType
.Page(25, 15);
var f5 = builder.Function("Get");
f5.Parameter<int>("key").Required();
f5.ReturnsFromEntitySet<UserDto>(nameof(DataListContext.Users));
#endregion
return builder.GetEdmModel();
}
}

Related

Data variable is equal to null, even though there are elements in the database

I'm seeding sql tables with the use of context factory, It shows up and there is an element in mssql, but when I write a controller, I get an empty list with no entries in the table. Could it perhaps be the inheritance that is causing an issue? Classical concert is inherited from Concert.
ConcertController:
public class ConcertController : Controller
{
private readonly AppDbContext _context;
public ConcertController(AppDbContext context)
{
_context = context;
}
public IActionResult Index()
{
var data = _context.Concerts.ToList(); //this variable is null
return View();
}
}
DbContext:
public class AppDbContext : DbContext
{
public virtual DbSet<Concert> Concerts { get; set; }
public virtual DbSet<Ticket> Tickets { get; set; }
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Party>().HasBaseType<Concert>();
modelBuilder.Entity<ClassicalConcert>().HasBaseType<Concert>();
base.OnModelCreating(modelBuilder);
}
}
Data seeding:
var factory = new AppContextFactory();
using var context = factory.CreateDbContext();
var builder = WebApplication.CreateBuilder(args);
await AddData();
async Task AddData()
{
ClassicalConcert mozart;
await context.AddRangeAsync(new[]
{
mozart = new ClassicalConcert()
{
PerformerName = "Some random dude",
TicketsCount= 1000,
PerformanceDate= DateTime.Now,
Location = "Rnd location",
Description = "some rnd desc",
ImageURL = "some https",
VoiceType = Centaurea.Data.Enums.VoiceTypes.Bass,
ConcertName = "Mozarts back",
ComposersName = "Mozart",
}
});
await context.SaveChangesAsync();
}
Context Factory:
public class AppContextFactory : IDesignTimeDbContextFactory<AppDbContext>
{
public AppDbContext CreateDbContext(string[] args = null)
{
var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>();
optionsBuilder.UseSqlServer(configuration["ConnectionStrings:DefaultConnection"]);
return new AppDbContext(optionsBuilder.Options);
}
}
Picture of the Debuger:

xUnit, Moq with UnitOfWork and general repository

I tried a lot of examples but I do not get the good response.
The ReportUpload method creates some Report entity based on the list from ExcelManager list
and adds them to the Reports DbSet.
My goal would be to read out the added entities from the mocked DbSet and assert them.
How can I pass the mocked DbContext and Dbset into UnitOfWork, please?
GeneralRepository.cs
public class GeneralRepository<TContext, TEntity> : IGeneralRepository<TEntity>
where TEntity : class
where TContext : DbContext
{
protected readonly TContext _context;
protected readonly DbSet<TEntity> dbSet;
public GeneralRepository(TContext context)
{
_context = context;
dbSet = _context.Set<TEntity>();
}
public async Task AddAsync(TEntity entity)
{
await _context.Set<TEntity>().AddAsync(entity);
}
...
}
IUnitOfWork.cs
public interface IUnitOfWork : IDisposable
{
IGeneralRepository<Report> Reports{ get; }
...
}
UnitOfWork.cs
public sealed class UnitOfWork : IUnitOfWork
{
private readonly MyDbContext _context;
public UnitOfWork(MyDbContext context)
{
_context = context;
}
private IGeneralRepository<Report>_report;
public IGeneralRepository<Report> Reports => _report ??= new GeneralRepository<MyDbContext, Report>(_context);
...
}
ReportService.cs
public class ReportService : GeneralService, IReportService
{
private readonly IExcelManager _excelManager;
public ReportService(IUnitOfWork unitOfWork, IExcelManager excelManager)
{
UnitOfWork = unitOfWork;
_excelManager = excelManager;
}
public async Task<string> ReportUpload(MemoryStream ms)
{
var workingList = _excelManager.ReadExcel(ms);
var i = 0;
while (i < workingList.Count)
{
var report = new Report { ... }
await UnitOfWork.Reports.AddAsync(report);
}
....
}
ReportServiceTest.cs
public class ReportServiceTests
{
[Fact()]
public async Task ReportUploadTest()
{
//Arrange
....
var mockSet = new Mock<DbSet<Report>>();
var mockContext = new Mock<MyDbContext>();
mockContext.Setup(x => x.Reports).Returns(mockSet.Object);
var reportRepositoryMock = new Mock<IGeneralRepository<Report>>();
reportRepositoryMock.Setup(m => m.AddAsync(It.IsAny<Report>()));
var unitOfWorkMock = new Mock<IUnitOfWork>();
unitOfWorkMock.Setup(p => p.Reports)
.Returns(reportRepositoryMock.Object);
...
//Act
var reportService = new ReportService(unitOfWorkMock.Object,exelManagerMock.Object);
await reportService.ReportUpload(new MemoryStream());
//Assert
???
}
DbContext cannot be passed to the UnitOfWork object because its context field is private.
I had to use SQLite in memory to test the GeneralRepository method.
var exelManagerMock = new Mock<IExcelManager>();
exelManagerMock.Setup(p => p.ReadExcel(It.IsAny<MemoryStream>()))
.Returns(listOfExcelReadResult);
var dbFixture = new DatabaseFixture();
var context = dbFixture.CreateContext();
var unitOfWork = new UnitOfWork(context);
//I need some plus data
await unitOfWork.Providers.AddRangeAsync(providers);
await context.SaveChangesAsync();
var reportService = new ReportService(unitOfWork, exelManagerMock.Object);
await reportService.ReportUpload(new MemoryStream(), 2021);
var allReports = await reportService.UnitOfWork.Reports.GetAsync();
Assert.Equal(3, allReports.Count);

Admin lock or unlock account user in .Net Core

I am doing the management of a user's account when necessary I can Lock a user's account in case they violate it. Or can be unlocked if required. I got an error like this. Where am I wrong, I use .Net Core 5 to build my program. Error: "An unhandled exception occurred while processing the request.
NullReferenceException: Object reference not set to an instance of an object."
enter image description here
Interface
public bool LockUser(string email);
public bool UnlockUser(string email);
Repo
public bool LockUser(string email)
{
var userTask = _userManager.FindByEmailAsync(email);
userTask.Wait();
var user = userTask.Result;
var lockUserTask = _userManager.SetLockoutEnabledAsync(user, true);
lockUserTask.Wait();
var lockDateTask = _userManager.SetLockoutEndDateAsync(user, DateTimeOffset.Now);
lockDateTask.Wait();
return lockDateTask.Result.Succeeded && lockUserTask.Result.Succeeded;
}
Controller
public ActionResult LockUser(string email)
{
if (!_userRepository.LockUser(email))
{
throw new ArgumentException("Error");
}
return RedirectToAction("Index");
}
Please refer the following sample code, the UserRepository should like this, add the usermanager via the constructor parameter:
public interface IUserRepository
{
public bool LockUser(string email);
public bool UnlockUser(string email);
}
public class UserRepository : IUserRepository
{
private readonly UserManager<IdentityUser> _userManager;
public UserRepository(UserManager<IdentityUser> userManager)
{
_userManager = userManager;
}
public bool LockUser(string email)
{
var userTask = _userManager.FindByEmailAsync(email);
userTask.Wait();
var user = userTask.Result;
var lockUserTask = _userManager.SetLockoutEnabledAsync(user, true);
lockUserTask.Wait();
var lockDateTask = _userManager.SetLockoutEndDateAsync(user, DateTimeOffset.Now);
lockDateTask.Wait();
return lockDateTask.Result.Succeeded && lockUserTask.Result.Succeeded;
}
public bool UnlockUser(string email)
{
//...
throw new NotImplementedException();
}
}
Then, add the service to the service container:
public void ConfigureServices(IServiceCollection services)
{
//...
services.AddScoped<IUserRepository, UserRepository>();
services.AddControllersWithViews();
}
Then, in the MVC controller:
public class HomeController : Controller
{
private readonly IUserRepository _userRepository;
public HomeController(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public IActionResult Index(int id)
{
string email = "aa#hotmail.com";
if (!_userRepository.LockUser(email))
{
throw new ArgumentException("Error");
}
return View();
}
The debug screenshot like this:

How can I pass the authenticated User Id to the class library project in asp.net core via DI?

I have a NLayers application:
asp.net core mvc
asp.net web api
and some of my class libraries:
DataLayer
DomainClasses
Models
Services
here is my BaseService in ServicesLayer:
public abstract partial class BaseService
{
protected BaseService(AppDbContext dbContext
, UserManager<MyApplicationUser> userManager
, int authenticatedUserId)
{
DbContext = dbContext;
AuthenticatedUserId = authenticatedUserId;
MyUserManager = userManager;
Init();
}
public AppDbContext DbContext { get; }
protected UserManager<MyApplicationUser> MyUserManager;
public string AuthenticatedUserId { get; }
protected virtual void Init()
{
//
}
...
}
and one of my child service classes:
public class BookService :BaseService
{
public BookService(AppDbContext dbContext
, UserManager<MyApplicationUser> userManager
, int authenticatedUserId)
:base(dbContext,userManager, authenticatedUserId)
{
}
}
I want to access the authenticated user id (from Asp net core) in my services (class library). How can I pass it via DI or something else?
Updated based on #Frank's suggestion:
public class CommonServicesContainer
{
public AppDbContext DbContext { get; set; }
public AppUserManager UserManager { get; set; }
public int AuthenticatedUserId{ get; set; }
public CommonServicesContainer(AppDbContext appDbContext, AppUserManager userManager, string authenticatedUserId)
{
DbContext = dbContext;
UserManager = userManager;
AuthenticatedUserId = autheticatedUserId;
}
}
my startup:
services.AddScoped<AppDbContext>();
services.AddScoped<AppUserManager>();
services.AddScoped(x =>
{
var authenticatedUserId = x.GetRequiredService<IHttpContextAccessor>().HttpContext.User.Identity.Name;
return new CommonServicesContainer(x.GetRequiredService<AppDbContext>()
, x.GetRequiredService<AppUserManager>()
, authenticatedUserId);
});
AccountController :
private readonly CommonServicesContainer _commonServicesContainer;
public AccountController(CommonServicesContainer commonServicesContainer)
{
_commonServicesContainer = commonServicesContainer;
// ...
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginInputModel model)
{
// ...
if(ModelState.IsValid)
{
var isValid = await _usersService.AreCredentialsValidAsync(model.Username, model.Password);
if(isValid)
{
var foundUser = await _usersService.GetByUserNameAsync(model.Username);
await HttpContext.SignInAsync(
foundUser.SubjectId,
foundUser.UserName);
//_commonServicesContainer.AuthenticatedUserId = foundUser.Id;
// ...
}
// ...
}
You can do that by register a AuthenticatedUser type as a AddScoped.
class AuthenticatedUser {
public int? UserId {get;set;}
public bool IsAuthenticated => int.HasValue;
}
in Startup.cs of your AspNetCore project:
public IServiceProvider ConfigureServices(IServiceCollection services) {
...
services.AddScoped<AuthenticatedUser>();
...
}
Somewhere you do the authentication, you get the AuthenticatedUser and set the UserId.
Since AuthenticatedUser is added as scoped it acts as global (same instance) for the particular httprequest scope. So all .GetService<AuthenticatedUser> / .GetRequiredService<AuthenticatedUser> will have the same instance - within the same scope.
Each http-request has it is own scope, and thereby also their own AuthenticatedUser.
When the user is Authenticated, using AspNetCore Identity, you can find the AspNetUsers Id by:
if( httpContext.User.Identity.IsAuthenticated ) {
var userIdClaim = httpContext.User.Claims.SingleOrDefault(c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier");
var aspNetUsersId = userIdClaim?.Value ?? 0;
}
This can be done as Middleware, then setting AuthenticatedUser.UserId.

GraphQL authentication with Asp.net core using JWT

I am using for GraphQL for .NET package for graphql. But I couldn't understand how can I authentication with JWT in graphql query or mutation.
I read the guide about authorization but I couldn't accomplish.
I need help with GraphQL for .NET authentication.
Any help will be appreciated.
Thanks
The guide is around authorization. The step you're looking for is the authentication and since graphql can be implemented using a ASP.Net API controller, you can implement JWT authentication as you would with any controller.
Here is a sample grapql controller using an Authorize attribute. You could, however, implement this using filter or if you want full control, custom middleware.
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class GraphQLController : ControllerBase
{
private readonly IDocumentExecuter executer;
private readonly ISchema schema;
public GraphQLController(IDocumentExecuter executer, ISchema schema)
{
this.executer = executer;
this.schema = schema;
}
[HttpPost]
public async Task<ActionResult<object>> PostAsync([FromBody]GraphQLQuery query)
{
var inputs = query.Variables.ToInputs();
var queryToExecute = query.Query;
var result = await executer.ExecuteAsync(o => {
o.Schema = schema;
o.Query = queryToExecute;
o.OperationName = query.OperationName;
o.Inputs = inputs;
o.ComplexityConfiguration = new GraphQL.Validation.Complexity.ComplexityConfiguration { MaxDepth = 15};
o.FieldMiddleware.Use<InstrumentFieldsMiddleware>();
}).ConfigureAwait(false);
return this.Ok(result);
}
}
public class GraphQLQuery
{
public string OperationName { get; set; }
public string Query { get; set; }
public Newtonsoft.Json.Linq.JObject Variables { get; set; }
}
In the Startup.cs I have configured JWT bearer token authentication.
Hope this helps.
I myself struggled for two days as well. I'm using https://github.com/graphql-dotnet/authorization now with the setup from this comment (from me): https://github.com/graphql-dotnet/authorization/issues/63#issuecomment-553877731
In a nutshell, you have to set the UserContext for the AuthorizationValidationRule correctly, like so:
public class Startup
{
public virtual void ConfigureServices(IServiceCollection services)
{
...
services.AddGraphQLAuth(_ =>
{
_.AddPolicy("AdminPolicy", p => p.RequireClaim("Role", "Admin"));
});
services.AddScoped<IDependencyResolver>(x => new FuncDependencyResolver(x.GetRequiredService));
services.AddScoped<MySchema>();
services
.AddGraphQL(options => { options.ExposeExceptions = true; })
.AddGraphTypes(ServiceLifetime.Scoped);
...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider)
{
...
app.UseMiddleware<MapRolesForGraphQLMiddleware>(); // optional, only when you don't have a "Role" claim in your token
app.UseGraphQL<MySchema>();
...
}
}
public static class GraphQLAuthExtensions
{
public static void AddGraphQLAuth(this IServiceCollection services, Action<AuthorizationSettings> configure)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IAuthorizationEvaluator, AuthorizationEvaluator>();
services.AddTransient<IValidationRule, AuthorizationValidationRule>();
services.AddTransient<IUserContextBuilder>(s => new UserContextBuilder<GraphQLUserContext>(context =>
{
var userContext = new GraphQLUserContext
{
User = context.User
};
return Task.FromResult(userContext);
}));
services.AddSingleton(s =>
{
var authSettings = new AuthorizationSettings();
configure(authSettings);
return authSettings;
});
}
}
public class GraphQLUserContext : IProvideClaimsPrincipal
{
public ClaimsPrincipal User { get; set; }
}
public class MapRolesForGraphQLMiddleware
{
private readonly RequestDelegate _next;
public MapRolesForGraphQLMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
// custom mapping code to end up with a "Role" claim
var metadata = context.User.Claims.SingleOrDefault(x => x.Type.Equals("metadata"));
if (metadata != null)
{
var roleContainer = JsonConvert.DeserializeObject<RoleContainer>(metadata.Value);
(context.User.Identity as ClaimsIdentity).AddClaim(new Claim("Role", string.Join(", ", roleContainer.Roles)));
}
await _next(context);
}
}
public class RoleContainer
{
public String[] Roles { get; set; }
}