ASP.NET Boilerplate: Get and GetAll is POST instead of GET method - c#

I'm using the ASP.NET Boilerplate framework to do a small project and I used Swagger UI Integration to check the API within the project. I noticed that the Get and GetAll are still using POST method while the others are in their respective HTTP verb for example Create is POST, Edit is UPDATE, etc... I read the documentation and tried what they suggested in this [Documentation]
(https://aspnetboilerplate.com/Pages/Documents/Dynamic-Web-API#http-verbs)
Edit 1: Code for UserAppServiceClass
[AbpAuthorize(PermissionNames.Pages_Users)]
public class UserAppService : AsyncCrudAppService<User, UserDto, long, PagedResultRequestDto, CreateUserDto, UpdateUserDto>, IUserAppService
{
private readonly UserManager _userManager;
private readonly RoleManager _roleManager;
private readonly IRepository<Role> _roleRepository;
public UserAppService(
IRepository<User, long> repository,
UserManager userManager,
IRepository<Role> roleRepository,
RoleManager roleManager)
: base(repository)
{
_userManager = userManager;
_roleRepository = roleRepository;
_roleManager = roleManager;
}
[HttpGet]
public override async Task<UserDto> Get(EntityDto<long> input)
{
var user = await base.Get(input);
var userRoles = await _userManager.GetRolesAsync(user.Id);
user.Roles = userRoles.Select(ur => ur).ToArray();
return user;
}
[HttpPost]
public override async Task<UserDto> Create(CreateUserDto input)
{
CheckCreatePermission();
var user = ObjectMapper.Map<User>(input);
user.TenantId = AbpSession.TenantId;
user.Password = new PasswordHasher().HashPassword(input.Password);
user.IsEmailConfirmed = true;
//Assign roles
user.Roles = new Collection<UserRole>();
foreach (var roleName in input.RoleNames)
{
var role = await _roleManager.GetRoleByNameAsync(roleName);
user.Roles.Add(new UserRole(AbpSession.TenantId, user.Id, role.Id));
}
CheckErrors(await _userManager.CreateAsync(user));
return MapToEntityDto(user);
}
[HttpPut]
public override async Task<UserDto> Update(UpdateUserDto input)
{
CheckUpdatePermission();
var user = await _userManager.GetUserByIdAsync(input.Id);
MapToEntity(input, user);
CheckErrors(await _userManager.UpdateAsync(user));
if (input.RoleNames != null)
{
CheckErrors(await _userManager.SetRoles(user, input.RoleNames));
}
return await Get(input);
}
[HttpDelete]
public override async Task Delete(EntityDto<long> input)
{
var user = await _userManager.GetUserByIdAsync(input.Id);
await _userManager.DeleteAsync(user);
}
[HttpGet]
public async Task<ListResultDto<RoleDto>> GetRoles()
{
var roles = await _roleRepository.GetAllListAsync();
return new ListResultDto<RoleDto>(ObjectMapper.Map<List<RoleDto>>(roles));
}
protected override User MapToEntity(CreateUserDto createInput)
{
var user = ObjectMapper.Map<User>(createInput);
return user;
}
protected override void MapToEntity(UpdateUserDto input, User user)
{
ObjectMapper.Map(input, user);
}
protected override IQueryable<User> CreateFilteredQuery(PagedResultRequestDto input)
{
return Repository.GetAllIncluding(x => x.Roles);
}
protected override async Task<User> GetEntityByIdAsync(long id)
{
var user = Repository.GetAllIncluding(x => x.Roles).FirstOrDefault(x => x.Id == id);
return await Task.FromResult(user);
}
protected override IQueryable<User> ApplySorting(IQueryable<User> query, PagedResultRequestDto input)
{
return query.OrderBy(r => r.UserName);
}
protected virtual void CheckErrors(IdentityResult identityResult)
{
identityResult.CheckErrors(LocalizationManager);
}
}
Edit 2:

Your Get method has complex input argument: Get(EntityDto<long> input). HTTP GET verb can't pass it to server, that is why ASP.NET Boilerplate has no choice, it decide to assign POST verb instead. Only parameters like /Get?name=Max&age=22 are allowed for GET - at this case method's signature will be like this: Get(string name, int age).
So you should leave all of it without changes or change signature to flattern variant, enumerating properties of EntityDto<long> as comma separated arguments.

You have to write Http Verb attributes to the interface. Not to the method implementations!
public interface IUserAppService: IApplicationService
{
[HttpGet]
GetUserOutput Get(GetInput input);
[HttpGet]
GetAllOutput GetAll(GetAllInput input);
}
Do it for IAsyncCrudAppService
PS: For the methods come from base, you can new them in your interface.

Related

Variable in Authorization Attribute .net in base Controller

In my application, I'm trying to build an authorization - privilege based authentication.
For better understanding here's how my privileges are named and stored in my database : "GET_USERS" , "GET_USER", "DELETE_USER" ...
What I want to do is to specify the privilege name in the authorization attribute from my base controller, but the problem is that the authorization attribute only allows constant parameters.
Here's my base Controller :
public class BaseController<T> : ControllerBase where T : class
{
protected readonly ConcreetDataContext _context;
protected readonly IBaseRepository<T> _repo;
protected readonly INotificationRepository _notificationRepo;
protected readonly IHubContext<NotificationHub> _hubContext;
protected readonly DbSet<T> _dbSet;
public BaseController(IHubContext<NotificationHub> hubContext,
ConcreetDataContext context,
IBaseRepository<T> repo,
INotificationRepository notifRepo)
{
_context = context;
_hubContext = hubContext;
_repo = repo;
_dbSet = _context.Set<T>();
_notificationRepo = notifRepo;
}
// GET: api/[items]
[HttpGet]
// HERE's THE ISSUE
[PrivilegeAuthorize("GET_"+typeof(T).Name.toUpper()] // this is not allowed
public async Task<ActionResult<IEnumerable<T>>> Get([FromQuery] GlobalParams globalParams)
{
Type t = typeof(T);
Console.WriteLine(t.Name.ToUpper());
var classes = await PagedList<T>.CreateAsync(_repo.Get(globalParams),globalParams.PageNumber,globalParams.PageSize);
Response.AddPagination(classes.CurrentPage, classes.PageSize, classes.TotalCount, classes.TotalPages);
return Ok(classes);
}
// GET: api/[items]/5
[HttpGet("{id}")]
public virtual async Task<ActionResult<T>> Get(int id)
{
var item = await this._repo.GetByID(id);
if (item == null)
{
return NotFound();
}
return item;
}
// PUT: api/[items]/5
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
[HttpPut("{id}")]
public async Task<IActionResult> Put(int id, T item)
{
// if (id != item.ID)
// {
// return BadRequest();
// }
try
{
await this._repo.Update(item);
// Creating the notification
await this._notificationRepo.CreateNotification("Update",typeof(T).ToString(),"Updated "+typeof(T).ToString()+" with ID : "+id);
}
catch (DbUpdateConcurrencyException)
{
if (!(await Exists(id)))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
// POST: api/Classes
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
[HttpPost]
public async virtual Task<ActionResult<T>> Post(T item)
{
await this._repo.Insert(item);
await this._notificationRepo.CreateNotification("Create",typeof(T).ToString(),"Created "+typeof(T).ToString());
return CreatedAtAction("Get", item);
}
// DELETE: api/Classes/5
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
var item = await _dbSet.FindAsync(id);
if (item == null)
{
return NotFound();
}
_dbSet.Remove(item);
await _context.SaveChangesAsync();
var notification = await this._notificationRepo.CreateNotification("Delete",typeof(T).ToString(),"Deleted "+typeof(T).ToString());
// Invoking BroadCastToUserFunction
var useID = Request.HttpContext.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value;
await _hubContext.Clients.User(useID).SendAsync("simo",notification);
return NoContent();
}
private async Task<bool> Exists(int id)
{
var item = await this._repo.GetByID(id);
if(item != null)
return true;
return false;
}
[HttpGet("count")]
public async Task<ActionResult<int>> Count([FromQuery] GlobalParams globalParams)
{
return await this._repo.Count(globalParams);
}
}
Thank you in advance !
As Lasse V. Karlsen pointed out in comments: "base class is compiled once, it is not compiled for each Variant, so the compiler will have to figure out what to pass as a string there once".
So I went with the following solution for the moment :
[PrivilegeAuthorize("GET_USERS")]
public override Task<ActionResult<IEnumerable<City>>> Get([FromQuery] GlobalParams globalParams)
{
return base.Get(globalParams);
}
I should override each method where I want to apply this authorization attribute.

cannot make changes in context persist

I am working on a simple 3-tier MVC app, and cannot make context changes persist. Here is my code:
Controller:
[HttpPost]
public IActionResult Register(UserDto user)
{
userService.Register(user);
return Ok("profile");
}
Service:
public void Register(UserDto userDto)
{
User user = new User();
user.FirstName = userDto.FirstName;
user.LastName = userDto.LastName;
user.Type = (UserType) userDto.Type;
unitOfWork.UserRepository.AddAsync(user);
}
Repository:
public Repository(ApplicationDbContext dbContext)
{
this.dbContext = dbContext;
this.dbSet = dbContext.Set<T>();
}
public async Task AddAsync(params T[] entities)
{
await dbSet.AddRangeAsync(entities);
await SaveChanges();
}
private async Task SaveChanges()
{
await dbContext.SaveChangesAsync();
}
Using my debugger, i found out that the dbset.AddRangeAsync does add the entity i set in the request into my dbContext. The problem is, on the next request, the entity is gone from the dbContext, and also does not appear in my database.Can somebody can explain me what am i doing wrong?
Your add method is asynchronous.other methods that call this method must be asynchronous.
public async Task Register(UserDto userDto)
{
User user = new User();
user.FirstName = userDto.FirstName;
user.LastName = userDto.LastName;
user.Type = (UserType) userDto.Type;
await unitOfWork.UserRepository.AddAsync(user);
}
[HttpPost]
public async Task<IActionResult> Register(UserDto user)
{
await userService.Register(user);
return Ok("profile");
}

how to override a method of Microsoft Identity?

I am newbie in C# and .Net
I am using a getter of SignInManager in one of my controllers like this:
public CustomSignInManager SignInManager
{
get
{
return HttpContext.GetOwinContext().Get<CustomSignInManager >();
}
}
and the CustomSignInManager class is like this:
public class OnlineSmSignInManager : SignInManager<User, int>
{
public OnlineSmSignInManager(OnlineSmUserManager userManager, IAuthenticationManager authenticationManager)
: base(userManager, authenticationManager)
{ }
}
there is a method in Identity's SignInManager class like this:
public virtual async Task<token> SendTwoFactorCodeAsync(string provider)
{
TKey userId = await this.GetVerifiedUserIdAsync().WithCurrentCulture<TKey>();
if ((object) userId == null)
return false;
string token = await this.UserManager.GenerateTwoFactorTokenAsync(userId, provider).WithCurrentCulture<string>();
IdentityResult identityResult = await this.UserManager.NotifyTwoFactorTokenAsync(userId, provider, token).WithCurrentCulture<IdentityResult>();
return token;
}
how can I override it to be like this:
In C# virtual members can be overriden by replacing the word virtual in the derived class:
public override async Task<bool> SendTwoFactorCodeAsync(string provider)
{
TKey userId = await this.GetVerifiedUserIdAsync().WithCurrentCulture<TKey>();
if ((object) userId == null)
return false;
string token = await this.UserManager.GenerateTwoFactorTokenAsync(userId, provider).WithCurrentCulture<string>();
IdentityResult identityResult = await this.UserManager.NotifyTwoFactorTokenAsync(userId, provider, token).WithCurrentCulture<IdentityResult>();
return true;
}
That goes in your CustomSignInManager class.
Here is a reference to the C# Language Specification
One thing to notice is that the return type has to be same.
So in your case you might want to create a new method rather than overriding the method.
There is no return type covariance in C#
It's not possible to change the return type when overriding a method.
C#: Overriding return types
Microsoft has an open issue for it on GitHub though: https://github.com/dotnet/csharplang/issues/49
It's not marked for implementation/release anytime soon though.

Get userId from JWT on all Controller methods?

I am creating a Core 2.0 Web API project that uses JWT for authentication and authorization. My controller methods that I want to secure are all decorated with the Authorize attribute.
This is working. If I pass the JWT in the Bearer header, I get a 200. If I fail to pass the JWT, I get the 401. All working. In my JWT, I have stored the User ID in the 'UserId' field when authorizing..
var claimsdata = new[] {
new Claim("UserId", user.Id.ToString()),
I then have an extension method:
public static string GetUserId(this IPrincipal user)
{
if (user == null)
return string.Empty;
var identity = (ClaimsIdentity)user.Identity;
IEnumerable<Claim> claims = identity.Claims;
return claims.FirstOrDefault(s => s.Type == "UserId")?.Value;
}
On my controller method, with 'Authorize', I often need the ID of the user. So I call my GetUserId method. This works. However, I am unsure if this is the best way to get the Id from the token.
int.TryParse(User.GetUserId(), out _userId);
I need to use that code on all controllers. I can't do it in the constructor, as .. that's wrong I think.
Am I doing the right thing here?
ControllerBase contains User property that is type of ClaimsPrincipal
You can access user claims by User.Claims and no need for IPrincipal
Create a base controller which contains GetUserId method as protected
public abstract class BaseController : Controller
{
protected int GetUserId()
{
return int.Parse(this.User.Claims.First(i => i.Type == "UserId").Value);
}
}
And all controllers inherit form this, now all controllers can access UserId
Firstly I create IUserProvider interface with IHttpContextAccessor injection to make mocks for these interfaces in unit tests.
public interface IUserProvider
{
string GetUserId();
}
Than implementation is
public class UserProvider : IUserProvider
{
private readonly IHttpContextAccessor _context;
public UserProvider (IHttpContextAccessor context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
}
public string GetUserId()
{
return _context.HttpContext.User.Claims
.First(i => i.Type == ClaimTypes.NameIdentifier).Value;
}
}
So you can use interface IUserProvider in your controller without inheritance
[Authorize]
[ApiController]
public class MyController : ControllerBase
{
private readonly IUserProvider _userProvider;
public MyController(IUserProvider userProvider)
{
_userProvider = userProvider ?? throw new ArgumentNullException(nameof(userProvider ));
}
[HttpGet]
[Route("api/My/Something")]
public async Task<ActionResult> GetSomething()
{
try
{
var userId= _userProvider.GetUserId();
}
}
}
Also you can use
Extension Method
like this
public static long GetUserID(this ClaimsPrincipal User)
{
return long.Parse(User.Claims.First(i => i.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier").Value);
}
and implement in your controller like this
[HttpDelete("DeleteAddress")]
public async Task<IActionResult> DeleteAddress([FromQuery] long AddressID)
{
try
{
long userID = this.User.GetUserID();
await _addressService.Delete(userID, AddressID);
return Ok();
}
catch (Exception err)
{
return Conflict(err.Message);
}
}
I hope it will help you
var authenticatedUser = User.Identities.Select(c => c.Claims).ToArray()[0].ToArray()[0];
var userid = await userManager.FindByNameAsync(authenticatedUser['email']).Id;

How to show unauthorized message in aspnetcore identity

I am using claim based authentication and authorization in aspnetcore 1.1.
If the user is not logged in, he gets fowarded to the login page as expected.
However, if the user is logged in, but does not have the correct claim, the user is forwarded back to the login page again.
How do I change that so it the user is routed to a different view which says "You are not authorized..."?
services.AddAuthorization(options=>
{
options.AddPolicy("IsEDIAdmin", policy =>
policy.RequireClaim("IsEDIAdmin"));
});
[Authorize(Policy = "IsEDIAdmin")]
public IActionResult App()
{
return PartialView();
}
I think it's a bit more complicated than it should be, but you should be able to create your own filter. For example (not tested but compiles):
public class ClaimRequirementAttribute : TypeFilterAttribute
{
public ClaimRequirementAttribute(string claim, string failUrl) : base(typeof(ClaimRequirementFilter))
{
Arguments = new object[] { claim, failUrl };
}
}
public class ClaimRequirementFilter : IAsyncActionFilter
{
private readonly string _claim;
private readonly string _failUrl;
public ClaimRequirementFilter(string claim, string failUrl)
{
_claim = claim;
_failUrl = failUrl;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (!context.HttpContext.User.Claims.Any(c => c.Value == _claim))
{
context.Result = new RedirectResult(_failUrl);
}
else
{
await next();
}
}
}
And use it like this:
[ClaimRequirement("IsEDIAdmin", "/some-exciting/url")]
public IActionResult Index()
{
//snip
}

Categories