How to use User.Identity.GetUserId() in Web Api 2 - entity-framework

I am using a post method where comedyId is coming from body of the request, while using postman i am getting redirected to login page again and again even after login.
Why login is not working for Api and User.Identity.GetUserId() is also returning null value ?
namespace ComedyCentral.Controllers.Api
{
[Authorize]
public class AttendancesController : ApiController
{
private ApplicationDbContext _context;
public AttendancesController()
{
_context = new ApplicationDbContext();
}
[HttpPost]
public IHttpActionResult Attend([FromBody] int comedyId)
{
var userId = User.Identity.GetUserId();
if (_context.Attendances.Any(a => a.AttendeeId == userId && a.ComedyId == comedyId))
return BadRequest("The attendance already exists.");
var attendance = new Attendance
{
ComedyId = comedyId,
AttendeeId = userId
};
_context.Attendances.Add(attendance);
_context.SaveChanges();
return Ok();
}
}
}

Related

Get current user id in dbcontext

I am working on an ASP.NET Core web application. This application works with a SQL Server database and I am using Entity Framework 6.
I want to automatically add, on each entity, a last modification date and user id.
My question is: how can I automatically set the user id to each entity?
I have tried to override SaveChanges() in the DbContext. It works fine, but I can't access the Microsoft identity classes there...
Thanks
(Apologies in advance if the method signatures look a bit odd, I'm one of the rare breed that uses explicit interface implementation to help avoid stale code dust-bunnies littering code as I re-factor)
An example on accessing a session state to get the user from within a DbContext using a session token from a Forms Authentication web application:
[Serializable]
public class UserDetails
{
public int UserId { get; set; }
public string DisplayName { get; set; }
}
public interface ISessionHelper
{
UserDetails CurrentUser { get; set; }
void Clear();
bool IsAuthenticated();
}
public class SessionHelper : ISessionHelper
{
private const string UserDetailsKey = "YourUniqueSessionIdKey";
private ISessionHelper This => this;
UserDetails ISessionHelper.CurrentUser
{
get
{
try
{
var token = (UserDetails)HttpContext.Current.Session[UserDetailsKey];
return token;
}
catch
{
throw new ApplicationException("The current session could not be resolved.");
}
}
set
{
try
{
HttpContext.Current.Session[UserDetailsKey] = value;
}
catch
{
throw new ApplicationException("The current session state could not be set.");
}
}
}
/// <summary>
/// <see cref="ISessionHelper.Clear"/>
/// </summary>
void ISessionHelper.Clear()
{
HttpContext.Current.Session.Clear();
}
/// <summary>
/// <see cref="ISessionHelper.IsAuthenticated(string)"/>
/// </summary>
bool ISessionHelper.IsAuthenticated()
{
try
{
var cookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
var ticket = FormsAuthentication.Decrypt(cookie.Value);
var userDetails = This.CurrentUser;
return (userDetails != null && userDetails.UserId != 0);
}
catch
{
return false;
}
}
}
public interface ICurrentUserLocator
{
bool IsUserLoggedIn { get; }
int CurrentUserId { get; }
string CurrentUserDisplayName { get; }
}
public sealed class FormsAuthenticationUserLocator : ICurrentUserLocator
{
private const string UnauthorizedAccessExceptionMessage = "No user is currently logged in.";
private readonly ISessionHelper _sessionHelper = null;
[ExcludeFromCodeCoverage]
private ICurrentUserLocator This => this;
private UserDetails _userDetails = null;
[ExcludeFromCodeCoverage]
private UserDetails CurrentUser
{
get { return _userDetails ?? (_userDetails = SessionHelper.UserDetails); }
}
[ExcludeFromCodeCoverage]
bool ICurrentUserLocator.IsUserLoggedIn => CurrentUser != null;
[ExcludeFromCodeCoverage]
int ICurrentUserLocator.CurrentUserId => CurrentUser?.UserId ?? throw new UnauthorizedAccessException(UnauthorizedAccessExceptionMessage);
[ExcludeFromCodeCoverage]
string ICurrentUserLocator.CurrentUserDisplayName => CurrentUser?.DisplayName ?? throw new UnauthorizedAccessException(UnauthorizedAccessExceptionMessage);
public FormsAuthenticationUserLocator(ISessionHelper sessionHelper)
{
_sessionHelper = sessionHelper ?? throw new ArgumentNullException("sessioNHelper");
}
}
Assuming you are using dependency injection with a container, register the two classes against their respective interfaces in the container on a per-request lifespan. Then add a dependency on ICurrentUserLocator into your DbContext:
public class AppDbContext : DbContext
{
private readonly ICurrentUserLocator _currentUserLocator = null;
public AppDbContext(ICurrentUserLocator currentUserLocator)
{
_currentUserLocator = currentUserLocator ?? throw new ArgumentNullException("currentUserLocator");
}
}
Now in your overridden SaveChanges you can fetch the current user ID:
var currentUser = Users.Single(x => x.UserId == _currentUserLocator.CurrentUserId);
var updatedEntities = ChangeTracker.Entries()
.Where(x => x.State == EntityState.Modified)
.Select(x => x.Entity)
.Cast<EditableEntityBase>();
foreach (var entity in updatedEntities)
{
entity.LastModifiedBy = currentUser;
entity.LastModifiedAt = DateTime.Now;
}
var insertedEntities = ChangeTracker.Entries()
.Where(x => x.State == EntityState.Added)
.Select(x => x.Entity)
.Cast<EditableEntityBase>();
foreach (var entity in insertedEntities)
{
entity.CreatedBy = entity.LastModifiedBy = currentUser;
entity.CreatedAt = entity.LastModifiedAt = DateTime.Now;
}
If you only expose the FKs rather than user navigation properties then you can just set the ID. If your app has a Log Out feature be sure to call the SessionHelper.Clear() method. In this example I modified the session helper I typically use to just focus on the current user details to keep it simple. It normally tracks a few other details, hence why the Clear() method wipes the entire session. It's a standardized wrapper for handling known session state rather than littering code with Session["somestring"] everywhere.

Error with insertion in postgres Database with my ASP.NET CORE API

I'm creating an ASP.NET CORE API and I have a problem when I want to insert data in my postgres database.
The insert data's doesn't auto increment themself and it always start with id = 0 even if i insert an other status
My controller is :
[Route("api/[controller]")]
[ApiController]
public class StatusController : ControllerBase
{
private readonly intranetApplicationAPIContext _context;
public StatusController(intranetApplicationAPIContext context)
{
_context = context;
}
[HttpGet]
public ActionResult<List<Status>> GetAll()
{
return _context.Status.AsNoTracking().ToList();
}
[HttpGet("{id}", Name = "GetStatus")]
public ActionResult<Status> GetById(long id)
{
var status = _context.Status.Find(id);
if (status == null)
{
return NotFound();
}
return status;
}
[HttpPost]
public IActionResult Create(Status status)
{
_context.Status.Add(status);
_context.SaveChanges();
return CreatedAtRoute("GetStatus", new { id = status.Id }, status);
}
Maybe the problem comes from .AsNoTracking() but I can't delete it because i will have this error :
The instance of entity type 'Status' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked
In my database, I see that I can do a sequence in order to auto increment but when I have to do my insert with this :
INSERT INTO status(state) VALUES ('test') RETURNING *;
How can I "translate" it in my statusController ?

REST API - CreatedAtRoute method doesn't return a value

I'm building some REST API server in .NET Core and using Postman software to test it. I have a problem with POST method which doesn't return me any value ("Could not get any response") when I try to perform second Add operation on my DBContext class inside CreateUser method. My code :
UsersController :
[Produces("application/json")]
[Route("api/[controller]")]
public class UsersController : Controller
{
private readonly DBContext _context;
#region CONSTRUCTOR
public UsersController(DBContext context)
{
_context = context;
}
#endregion
#region HTTP GET
// GET: api/users || api/users?cardnr=xxx
[HttpGet]
public async Task<IActionResult> GetUsers(string cardNr)
{
if (String.IsNullOrEmpty(cardNr))
{
try
{
var users = await _context.Users.ToListAsync();
if (users.Any())
{
return Json(users);
}
else
{
return NotFound();
}
}
catch (Exception ex)
{
Helpers.ExceptionLogger.LogException(ex);
return StatusCode(500);
}
}
else
{
try
{
var user = await _context.Users.FirstOrDefaultAsync(u => u.Cards.Any(c => c.CardNumber.Equals(cardNr)));
if (user == null)
{
return NotFound();
}
else
{
return new ObjectResult(user);
}
}
catch (Exception ex)
{
Helpers.ExceptionLogger.LogException(ex);
return StatusCode(500);
}
}
}
//GET: api/users/1
[HttpGet("{id}", Name = "GetUserByID")]
public async Task<IActionResult> GetUserByID(Int32 id)
{
try
{
var user = await _context.Users.FirstOrDefaultAsync(u => u.IDUser == id);
if (user == null)
{
return NotFound();
}
else
{
return new ObjectResult(user);
}
}
catch (Exception ex)
{
Helpers.ExceptionLogger.LogException(ex);
return StatusCode(500);
}
}
#endregion
#region HTTP POST
[HttpPost]
public async Task<IActionResult> CreateUser([FromBody] Models.User userToCreate, string userGroupID)
{
if (userToCreate == null)
{
return BadRequest();
}
else
{
try
{
_context.Users.Add(userToCreate);
int parsingResult;
// if user passed userGroupID
if (userGroupID != null)
{
// parsing if userGroupID is a number
if (!int.TryParse(userGroupID, out parsingResult))
{
return BadRequest();
}
else
{
// if client want to assign a new user to some group
if (parsingResult > 0)
{
// creating new record in UserGroup table - assigning a user to group
var userGroup = new Models.UserGroup();
_context.Entry(userGroup).Property("IDGroup").CurrentValue = parsingResult;
_context.Entry(userGroup).Property("IDUser").CurrentValue = userToCreate.IDUser;
_context.UserGroups.Add(userGroup); // NOTE HERE
}
}
}
await _context.SaveChangesAsync();
return CreatedAtRoute("GetUserByID", new { id = userToCreate.IDUser }, userToCreate);
}
catch (Exception ex)
{
Helpers.ExceptionLogger.LogException(ex);
return StatusCode(500);
}
}
}
#endregion
}
User model :
public class User
{
[Key]
public int IDUser { get; set; }
[Required]
public string Name { get; set; }
public List<UserGroup> UsersGroups { get; set; }
}
UserGroup model :
public class UserGroup
{
public Group Group { get; set; }
public User User { get; set; }
}
DBContext class :
public class DBContext : DbContext
{
public DBContext(DbContextOptions<DBContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// shadow property - foreign key
modelBuilder.Entity<UserGroup>()
.Property<int>("IDUser");
// shadow property - foreign key
modelBuilder.Entity<UserGroup>()
.Property<int>("IDGroup");
modelBuilder.Entity<UserGroup>()
.HasKey( new string[]{ "IDUser", "IDGroup" });
modelBuilder.Entity<UserGroup>()
.HasOne(ug => ug.Group)
.WithMany(g => g.UsersGroups)
.HasForeignKey("IDGroup");
modelBuilder.Entity<UserGroup>()
.HasOne(ug => ug.User)
.WithMany(u => u.UsersGroups)
.HasForeignKey("IDUser");
base.OnModelCreating(modelBuilder);
}
public DbSet<Group> Groups { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<UserGroup> UserGroups { get; set; }
}
The problem lies in HttpPost method in UsersController.
When I do "normal" POST and pass JSON object which contain a user to add without assigning it to group (empty userGroupID parameter) everything is ok - user gets added to the DataBase and Postman returns me a user with its ID.
screen :
and when I try to add a new user but with adding it to specific group I always get an error :
screen :
Even despite that error new user gets properly added to DB and associated with its group (record gets added to UserGroup table; UserGroup is join table between Users and Groups table). So I have proper data in my DB but I always get this error and I can't return new added user to client who called API and can't get his ID. Am I doing something wrong in my CreateUser method ?
UPDATE :
I have added a comment line in "NOTE HERE" in CreateUser method in UsersController. If I comment whole this line I don't get an error from Postman but obviously I don't get my User associated with its group (I don't get new record added to UserGroup join table). So it seems like another Add method on context object causing an error ... Does it make sense ?
Did you try to debug it?
Set a breakpoint on the row:
if (userToCreate == null)
Send again the request with Postman and debug your app. There you can see what and where it goes wrong.
Please let me know how it is going so I know how can I help you:)

What should I return from my service layer? DotNetOpenAuth

I am wondering what should I return in this case. My Ui and service layer are in different projects.
This is what happens
-> User Comes to Site -> User chooses openId provider and hits login -> Post back to controller
[HttpPost]
public ActionResult Login(LoginViewModel loginViewModel)
{
var test = accountService.SendOpenIdRequest(loginViewModel.OpenId);
}
public class LoginViewModel
{
public OpenId OpenId { get; set; }
}
So I take in a ViewModel that contains my domain class.
public class OpenId
{
public string Url { get; set; }
}
So far in my SendOpenIdRequest
public ? SendOpenIdRequest(OpenId openId)
{
var openIdRelyingParty = new OpenIdRelyingParty();
var response = openIdRelyingParty.GetResponse();
Identifier id;
if (Identifier.TryParse(openId.Url, out id))
{
try
{
var req = openIdRelyingParty.CreateRequest(openId.Url);
return req.RedirectingResponse
}
catch (ProtocolException ex)
{
}
}
return null;
}
Now this is where I get lost since their are so many things I could return.
I could return
return req.RedirectingResponse.AsActionResult()
However I think this would be bad as now I am depending on asp.net mvc ActionResult and if I say use this service layer for some other project(maybe I have a webservice that connects to a mobile application). It won't won't work to well.
I could return OutgoingWebResponse but I am not really sure what to do with it once I get it back.
I could also return the IAuthenticationRequest what is generated from CreateRequest()
Finally I could return my Domain Object(OpenId) with one of the ones I listed above in it.
You could return an OutgoingWebResponse:
public OutgoingWebResponse SendOpenIdRequest(OpenId openId)
{
using (var openIdRelyingParty = new OpenIdRelyingParty())
{
var response = openIdRelyingParty.GetResponse();
Identifier id;
if (Identifier.TryParse(openId.Url, out id))
{
try
{
var req = openIdRelyingParty.CreateRequest(openId.Url);
return req.RedirectingResponse
}
catch (ProtocolException ex)
{
}
}
return null;
}
}
and then in your controller:
[HttpPost]
public ActionResult Login(LoginViewModel loginViewModel)
{
var response = accountService.SendOpenIdRequest(loginViewModel.OpenId);
if (response == null)
{
ModelState.AddModelError(
"openid_identifier",
"The specified login identifier is invalid"
);
return View();
}
return response.AsActionResult();
}

Is it possible to send object as route value in RedirectToRouteResult?

First of all, it needs to create some codes that handle any error in my application like the following code.
public class HandleSomeErrorAttribute : HandleErrorAttribute
{
public string ControllerName { get; set; }
public string ActionName { get; set; }
public override void OnException(ExceptionContext filterContext)
{
base.OnException(filterContext);
if(filterContext.Result != null)
{
var viewResult = filterContext.Result as ViewResult;
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(
new
{
controller = ControllerName,
action = ActionName,
errorInfo = "test"
}
));
}
}
}
Everything works fine if I send test as errorInfo value. In the other hand, if I send some errorInfo object as errorInfo value, action controller will not receive any errorInfo value. It always is null.
I know this behavior is designed to handle any values in form collection that is sent by web browser when user submits form. So, it always read only string value and parses it to object.
So, is it possible to do that?
Thanks,
After I search some related question in Stackoverflow, I just realize that any redirect action in ASP.NET sends HTTP 302 code to browser. After that, browser will create new request to fetch new URL (that can be found in HTTP 302 header).
Therefore, it is impossible to directly send any complex objects (without serialize it) to browser and order browser to send it back to server. Although, it is possible, but I think it is so silly to do that. Because you can use the following code to call another action without send HTTP 302 to browser.
public class TransferResult : ActionResult
{
public TransferResult(string controllerName, string actionName, RouteValueDictionary routeValues = null)
{
RouteValues = routeValues ?? new RouteValueDictionary();
if (!string.IsNullOrEmpty(controllerName))
{
RouteValues[MvcApplication.ControllerRouteKey] = controllerName;
}
if (!string.IsNullOrEmpty(actionName))
{
RouteValues[MvcApplication.ActionRouteKey] = actionName;
}
if(RouteValues[MvcApplication.ControllerRouteKey] == null)
{
throw new ArgumentException(Resources.ControllerNameIsNotFoundInRouteValueDictionary, "controllerName");
}
if (RouteValues[MvcApplication.ActionRouteKey] == null)
{
throw new ArgumentException(Resources.ActionNameIsNotFoundInRouteValueDictionary, "actionName");
}
}
public TransferResult(RouteValueDictionary routeValues)
: this(null, null, routeValues)
{
}
public RouteValueDictionary RouteValues { get; set; }
public override void ExecuteResult(ControllerContext context)
{
var routeData = new RouteData();
foreach (var item in RouteValues)
{
routeData.Values.Add(item.Key, item.Value);
}
var contextWrapper = new HttpContextWrapper(HttpContext.Current);
var request = new RequestContext(contextWrapper, routeData);
var controller = ControllerBuilder.Current.GetControllerFactory().CreateController(context.RequestContext, RouteValues[MvcApplication.ControllerRouteKey].ToString());
controller.Execute(request);
}
}
Next, I create some handle error for handling some error type and transfering it to another action controller.
public class SomeHandleErrorAttribute : HandleErrorAttribute
{
public SomeHandleErrorAttribute (string controllerName, string actionName)
{
ExceptionType = typeof (EntityException);
ControllerName = controllerName;
ActionName = actionName;
}
public string ControllerName { get; set; }
public string ActionName { get; set; }
public override void OnException(ExceptionContext filterContext)
{
base.OnException(filterContext);
if(filterContext.Result != null)
{
var routeValue = new RouteValueDictionary
{
{"errorInfo", filterContext}
};
filterContext.Result = new TransferResult(ControllerName, ActionName, routeValue);
}
}
}
Finally, I create the action for handling this error.
public class ErrorController : Controller
{
public string EntityError(ExceptionContext errorInfo)
{
return string.Format("Handle error for {0} ", errorInfo.Exception.Message);
}
}
Nope. A redirect returns a string to your browser, informing it of the page (and query parameters) it should fetch. You can (and should!) watch this in Fiddler. So whatever you put into a redirect has to be serializable to/from a string.