I trying to create a web api with oData v4.
Now i try to get all Identity-User over oData.
This is working:
[EnableQuery]
public class UsersController : Controller
{
protected readonly UserManager<User> _userManager;
public UsersController(UserManager<User> userManager)
{
_userManager = userManager;
}
private static List<User> _users = new List<User>
{
new User { Id = 1, Name = "Flo", Email = ""},
new User { Id = 2, Name = "Felix", Email = ""},
new User { Id = 3, Name = "Andreas", Email = ""},
new User { Id = 4, Name = "Marco", Email = ""}
};
public IQueryable<User> Get()
{
return _users.AsQueryable();
}
}
And return this response:
{"#odata.context":"http://localhost:55503/oData/$metadata#Users(Id)","value":[{"Id":1},{"Id":2},{"Id":3},{"Id":4}]}
When i change the controller to return all Identity-Users this isn't working correctly.
[EnableQuery]
public class UsersController : Controller
{
protected readonly UserManager<User> _userManager;
public UsersController(UserManager<User> userManager)
{
_userManager = userManager;
}
public IQueryable<User> Get()
{
return _userManager.Users.AsQueryable();
}
}
And it returns this response:
[{"Id":"35909773-8b53-4d68-a770-b7cdfcffd0de"}]
But the response is missing the context. Can you give my a hint why?
I solved the problem:
var builder = new ODataConventionModelBuilder(serviceProvider);
builder.EntitySet<User>("Users");
return builder.GetEdmModel();
Related
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:
building new app on .net core 3 and Angular. Overall all works, but I want to add more intelligence to service/controller part. This is one of the api's, but this logic can be applied to others as as well.
Here's my Login Controller:
[HttpPost]
public async Task<IActionResult> Login([FromBody] UserLoginDto userLogin)
{
var token = await _userService.LoginAsync(userLogin);
if (token != null)
{
return Ok(token);
}
else
{
return BadRequest("Something went wrong");
}
}
And here's my userService:
public async Task<string> LoginAsync(UserLoginDto userLogin)
{
var user = await _userManager.FindByEmailAsync(userLogin.Email);
if (user != null)
{
var result = await _signInManager.PasswordSignInAsync(user, userLogin.Password, false, true);
if (result.Succeeded)
{
var roles = await _userManager.GetRolesAsync(user);
var tokenJson = _jwtManager.getJwtToken(user.Email, roles);
return tokenJson;
}
else
{
return null; // Return BadRequest and result reason (Failed, lockedout, etc)
}
}
else
{
return null; // User not found, return NotFound }
}
Here's my question - how should I return result from userService to Controller so, that I could respond to API call either with Ok(token) or BadRequest/NotFound with the reason.
If I keep all this LoginAsync code in controller, then it's easy, but I want to use service.
One option I was thinking was to introduce new class, something like:
public class BaseResult
{
public object Data { get; set; }
public long ResponseCode { get; set; }
public string ErrorMessage { get; set; }
}
then always return this class from service, but not fully like that idea either.
thanks!
Here is a working demo you could follow:
Model:
public class UserLoginDto
{
public string Email { get; set; }
public string Password { get; set; }
}
IUserService:
public interface IUserService
{
Task<IActionResult> LoginAsync(UserLoginDto userLogin);
}
UserService:
public class UserService: IUserService
{
private readonly UserManager<IdentityUser> _userManager;
private readonly SignInManager<IdentityUser> _signInManager;
private readonly IJwtManager _jwtManager;
public UserService(
UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager,
IJwtManager jwtManager)
{
_userManager = userManager;
_signInManager = signInManager;
_jwtManager = jwtManager;
}
public async Task<IActionResult> LoginAsync(UserLoginDto userLogin)
{
var user = await _userManager.FindByEmailAsync(userLogin.Email);
if (user != null)
{
var result = await _signInManager.PasswordSignInAsync(user, userLogin.Password, false, true);
if (result.Succeeded)
{
var roles = await _userManager.GetRolesAsync(user);
var tokenJson = _jwtManager.getJwtToken(user.Email, roles);
return new OkObjectResult(tokenJson);
}
else
{
// Return BadRequest and result reason (Failed, lockedout, etc)
if (result.IsNotAllowed)
{
if (!await _userManager.IsEmailConfirmedAsync(user))
{
// Email isn't confirmed.
return new BadRequestObjectResult("Email isn't confirmed.");
}
if (!await _userManager.IsPhoneNumberConfirmedAsync(user))
{
// Phone Number isn't confirmed.
return new BadRequestObjectResult("Phone Number isn't confirmed.");
}
return new BadRequestObjectResult("Login IsNotAllowed");
}
else if (result.IsLockedOut)
{
// Account is locked out.
return new BadRequestObjectResult("Account is locked out.");
}
else if (result.RequiresTwoFactor)
{
// 2FA required.
return new BadRequestObjectResult("2FA required");
}
else
{
// Password is incorrect.
return new BadRequestObjectResult("Password is incorrect.");
}
}
}
else
{
return new NotFoundObjectResult("Username is incorrect"); // User not found, return NotFound }
}
}
}
Controller:
public class HomeController : Controller
{
private readonly IUserService _userService;
public HomeController(IUserService userService)
{
_userService = userService;
}
[HttpPost]
public async Task<IActionResult> Login([FromBody] UserLoginDto userLogin)
{
var result= await _userService.LoginAsync(userLogin);
return result;
}
}
Startup.cs:
Not sure what is _jwtManager.getJwtToken in your code,so I just guess it is an interface and owns a JwtManager class implemented this interface.And it contains a getJwtToken method which generated the token.
services.AddScoped<IUserService, UserService>();
services.AddScoped<IJwtManager, JwtManager>();
I have added a few custom claims to my user and I was wondering if I want to check if these claims exist on controller actions using attributes, I know that we can create a class and extend attribute from .Net and the general idea is to check if user has claim or not, I'm not really clear on the implementation.
Maybe something like this:
[AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = false)]
public class ClaimRequirementAttribute : Attribute
{
public ClaimRequirementAttribute(string claimType)
{
new Claim(claimType, null);
}
}
public class ClaimRequirementFilter
{
public void OnAuthorization(HttpContext httpContext)
{
var hasClaim = httpContext.User.HasClaim(x => x.Type ==
CapabilityClaims.CanReadSpore);
if (!hasClaim)
{
}
}
}
You can get the Claims of a specific user, using the GetClaimsAsync method of UserManager.
You can use the following method:
public class TestController : Controller
{
private readonly UserManager<AppUser> _userManager;
public TestController(UserManager<AppUser> userManager)
{
_userManager = userManager;
}
public CheckIfClaimsExist(string email)
{
var user = await _userManager.FindByEmailAsync(email);
if(user != null)
{
var claims = await _userManager.GetClaimsAsync(user);
}
}
}
Note: AppUser class is a custom class which extends IdentityUser class from identity server.
After some long research i found this answer using filters
which ended up being the best approach
[AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = true)]
public class ClaimRequirementAttribute : TypeFilterAttribute
{
public ClaimRequirementAttribute(params string[] claimType) : base(typeof(ClaimRequirementFilter))
{
Arguments = new object[] { claimType };
}
}
public class ClaimRequirementFilter : IAuthorizationFilter
{
readonly string[] _claimTypes;
public ClaimRequirementFilter(string[] claimTypes)
{
_claimTypes = claimTypes;
}
public void OnAuthorization(AuthorizationFilterContext authContext)
{
if (authContext == null)
{
throw new ArgumentNullException(nameof(authContext));
}
var user = authContext.HttpContext.User;
var resourceId = authContext.RouteData.Values["id"].ToString();
var claimType = _claimTypes
.All(s => (user.Claims)
.Any(c => c.Type == s && (c.Value == resourceId || c.Value == string.Empty)));
if (user == null || !claimType)
{
authContext.Result = new ForbidResult();
}
}
I want to create a new user during the seed method of the program but how do I call CreateAsync??
public class DbInitializer
{
private readonly UserManager<ApplicationUser> _userManager;
public DbInitializer(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public static void Initialize(IApplicationBuilder app)
{
OneDbContext context = app.ApplicationServices.GetRequiredService<OneDbContext>();
context.Database.EnsureCreated();
//If there are no users, create a test user
if (!context.Users.Any())
{
var user = new ApplicationUser { UserName = "test", Email = "test#test.com" };
var result = _userManager.CreateAsync(user, "test");
}
error CS0120: An object reference is required for the non-static field, method, or property 'DbInitializer._userManager'
I want to take a few post query parameters from an API i have and create a new entry. I wanted to do this with in the method with out needing to load context or something.
namespace fais.printing_services.Controllers
{
[Produces("application/json")]
[Route("api/[controller]/[action]")]
public class printController : Controller
{
private readonly IHostingEnvironment _appEnvironment;
public printController(IHostingEnvironment appEnvironment)
{
_appEnvironment = appEnvironment;
}
/**/
[HttpPost]
public IActionResult request(string id="test_default", string url = "", string html = "")
{
print_job _print_job = new print_job();
_print_job.html = html;
_print_job.options = options; //json object string
_print_job.url = url;
using (ApplicationDbContext db = new ApplicationDbContext())
{
db.print_job.Add(_print_job);
db.SaveChanges();
}
return Json(new
{
save = true
});
}
}
}
I just want to be able create a new print_job entry and save it when the API is called and return a json response.
Add ApplicationDbContext to controller constructor, it will be injected automatically (if your Startup.cs is like recommeneded):
private readonly IHostingEnvironment _appEnvironment;
private readonly ApplicationDbContext _db;
public printController(IHostingEnvironment appEnvironment, ApplicationDbContext db)
{
_appEnvironment = appEnvironment;
_db = db;
}
[HttpPost]
public IActionResult request(string id="test_default", string url = "", string html = "")
{
var _print_job = new print_job()
{
html = html,
options = options,
url = url,
}
_db.print_job.Add(_print_job);
_db.SaveChanges();
return Json(new { save = true });
}