I am new to MVC5 and trying to implement sessions with in async methods in Controllers. I have created method like this
public async Task<ViewResult> Index()
{
string currentUserId = User.Identity.GetUserId();
var userId = Convert.ToInt64(HttpContext.Session["UserId"]);
var userDetail = await service.GetBy(currentUserId),
}
This session thing, always fails with the following message
Server Error in Application. Unable to cast object of type
'System.Threading.Tasks.Task`1[System.Int64]' to type
'System.IConvertible'.
(Sorry, I am not able to post screenshot of actual error, as do not have enough reputation points on this site)
I also tried to do it like this
var userId = await Task<long>.FromResult(Convert.ToInt64(HttpContext.Session["UserId"]));
and
var userId = await (Session["UserId"] as Task<long>);
and
var userId = await Convert.ToInt64(HttpContext.Session["UserId"]);
But, it always gives the same message. Can somebody please point me to the correct approach. Also, if we can not use session with in async methods, then what would be the best solution for it.
The error implies that HttpContext.Session["UserId"] is storing a Task instead of an actual int64. That means the somewhere in your code, where you store the userId in the session you are probably forgetting to await a task for its result.
Probably looks like:
HttpContext.Session["UserId"] = GetUserIdAsync();
Instead of:
HttpContext.Session["UserId"] = await GetUserIdAsync();
You could also await the stored task as you've tried to do (with the wrong type conversion):
var userId = await ((Task<Int64>)HttpContext.Session["UserId"]);
Related
Hope somebody can help me with this. I am trying to create a Change Password Post Request in the Account controller, but my user is always returning null. I'm new to .NET, could someone help up me figure out the best way to retrive the current user Id? Here is the code I have thus far.
[HttpPost("changePassword")]
public async Task<ActionResult<ChangePasswordDTO>> ChangePassword([FromBody] ChangePasswordDTO changePassword) {
var currentUserId = User.Claims.ToList()
.FirstOrDefault(x => x.Type == "").Value;
var user = await _userManager.FindByIdAsync(currentUserId);
var result = await _userManager.ChangePasswordAsync(user, changePassword.Password, changePassword.NewPassword);
if (result.Succeeded) {
return Ok("Password changed succesfully");
} else return Unauthorized("An error occurred while attempting to change password");
}
When I change the "currentUserId" to a hard coded value, everything works fine, of course. Every user has a generated jwt token with his name, id, roles etc. I'm unsure how to proceed, everything I've tried retrieves the user as null. Let me know if you need extra information, I'm pretty new to all of this and appreciate whatever help I can get, thanks!
Solution
JamesS pointed me in the right direction, this code works:
var currentUserId =this.User.FindFirstValue(ClaimTypes.NameIdentifier);
You can get the current user id in your controller using the UserManager such as:
ASP.NET CORE >= 2.0
var currentUserId = User.FindFirstValue(ClaimTypes.NameIdentifier);
or Name using:
var currentUserId = User.FindFirstValue(ClaimTypes.Name);
If you want to get the current user in some other class, you can use the IHttpContextAccessor, passing it into the class's constructor and using the HttpContext to access the User
I am trying create a post, I'm not quite sure why I get this error. An entity object cannot be referenced by multiple instances of IEntityChangeTracker, if someone could shed some light for me
This is the code in my Controller
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create(NewPostModel model)
{
var userId = User.Identity.GetUserId();
var user = await UserManager.FindByIdAsync(userId);
var post = BuildPost(model, user);
await PostService.Instance.Add(post);
return RedirectToAction("Index", "Post", new { id = post.Id });
}
private Post BuildPost(NewPostModel post, ApplicationUser user)
{
var now = DateTime.Now;
var forum = ForumServices.Instance.GetById(post.ForumId);
return new Post
{
Title = post.Title,
Content = post.Content,
Created = now,
Forum = forum,
User = user,
IsArchived = false
};
}
A here is my code in the PostService
public async Task Add(Post post)
{
using (var context = new ApplicationDbContext())
{
context.Posts.Add(post);
await context.SaveChangesAsync();
}
}
When I debug, the controller gets the model and the user, however when it gets to the await PostService.Instance.Add(post); it goes to the PostService then comes back to the await line in the controller then I get the error. I'm not sure what to do...
You get this exception because the forum and user property values belong to another Entity Framework context.
When you create your new Post you assign the Forum property to a forum instance. That instance came from ForumServices.Instance.GetById and is associated to a context. Then you add the Post object to another context, the forum instance is now associated to 2 contexts which is not possible.
To fix this, you can change the architecture to use a single EF context per request. It looks like you use a singleton (ForumServices.Instance) if you use .net core, you should have a look at how dependency injection works and use the AddScoped method to have a single EF context for the whole life time of the request. This is the solution I highly recommend.
If it is not possible to change the way context are created you can assign Id instead of full object
return new Post
{
Title = post.Title,
Content = post.Content,
Created = now,
ForumId = forum.ForumId,
UserId = user.UserId,
IsArchived = false
};
If it is still not possible you can return untracked entities. You can do this by using the AsNoTracking method or by using the QueryTrackingBehavior property of the context
context.Forums.AsNoTracking().First(f => f.ForumId == forumId);
// or
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
Thank you so much, I did as suggested by adding a ForumId and UserId to the models
return new Post
{
Title = post.Title,
Content = post.Content,
Created = now,
ForumId = post.ForumId,
UserId = user.Id,
IsArchived = false
};
I have a lot of tables in my database that use a user's identity User.Id as a foreign key. Now, in a lot of the requests I need to do the below lines it seems just in case the User is null (even if I add [Authorise] filter to the function).
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
ViewBag.ErrorCode = "1201";
ViewBag.ErrorMsg = "User not found";
return View("HandledError");
}
var userId = user.Id;
Does anyone know the best way I can access this without copying this same code for each function?
I guess I could have a function that retrieves the Id... and throws an exception on Null.. but then I would still need to write it in a try catch everytime anyway....
Any ideas?
User id is a claim. You can get it via:
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
Note: You'll need to add a using for System.Security.Claims.
In WebApi 2 you can use RequestContext.Principal from within a method on ApiController
string id;
id = User.Identity.GetUserId();
id = RequestContext.Principal.Identity.GetUserId();
Is there a way to remove a user from a role after a given timespan? When I try something like the below code, I get a null exception once the Delay continues in sessionExpired()...
public async Task<IActionResult> PurchaseSession(PurchaseSessionViewModel model)
{
var user = await _userManager.GetUserAsync(User);
await _userManager.AddToRoleAsync(user, "Active");
await _signInManager.RefreshSignInAsync(user);
// no await
sessionExpired(user);
return RedirectToAction(nameof(Index));
}
private async void sessionExpired(ApplicationUser user)
{
await Task.Delay(10000);
await _userManager.RemoveFromRoleAsync(user, "Active");
}
Note, I understand why the exception occurs but I'd like to retain this type of role-based authorization since [Authorize(Roles = "Active")] provides the functionality I'm after. Is there another way to do this?
Your problem is that your user variable is local and thus is deleted after your first function ends.
You may use a closure (as a lambda function). It is a block of code which maintains the environment in which it was created, so you can execute it later even if some variables were garbage collected.
EDIT: If you want to know why my previous solution didn't work, it is probably because user was being disposed by Identity at a time or another, so here is another try:
public async Task<IActionResult> PurchaseSession(PurchaseSessionViewModel model)
{
var user = await _userManager.GetUserAsync(User);
await _userManager.AddToRoleAsync(user, "Active");
await _signInManager.RefreshSignInAsync(user);
// We need to store an ID because 'user' may be disposed
var userId = user.Id;
// This create an environment where your local 'userId' variable still
// exists even after your 'PurchaseSession' method ends
Action sessionExpired = async () => {
await Task.Delay(10000);
var activeUser = _userManager.FindById(userId);
await _userManager.RemoveFromRoleAsync(activeUser, "Active");
};
Task.Run(sessionExpired);
return RedirectToAction(nameof(Index));
}
I am using Web Api Service to Pass the Data to my Mobile Devices.
There can be 2 scenario for this.
Single Device Requesting Multiple Times
Multiple Device Requesting Multiple Times
In Both the Scenario i am not able to handle multiple Request, I don't know what is the actual problem but it's keep giving me the 403 Response and 500 Response.
I need to handle Multiple request within seconds as I am Dealing with more then 1000 Devices at the same time and side by side i also need to respond them within seconds because we don't want that our devices wait for the Response for more then few Seconds.
Currently I am using Azure platform for the Web Api services and working with MVC 4.0 Using LINQ. If you want my code then i will provide you the Code (I am using repository Pattern in my Project)
Code
Controller :
[HttpPost]
public JObject GetData(dynamic data)
{
JObject p = new JObject();
try
{
MyModelWithObjectInData transactionbatchData = JsonConvert.DeserializeObject<MyModelWithObjectInData>(data.ToString());
return _batchDataService.batchAllDataResponse(transactionbatchData);//Call Repository
}
}
Repository :
public JObject batchAllDataResponse(MyModelWithObjectInData oData)
{
JObject p = new JObject();
var serviceResponseModel = new MyModelWithObjectInData();
using (var transaction = new TransactionScope())
{
//Insert in Tables
}
//Select Inserted Records
var batchData = (from p in datacontext.Table1
where p.PK == oData.ID select p).FirstOrDefault();
if (batchData != null)
{
serviceResponseModel.GetBatchDataModel.Add(new BatchDataModel //List Residing in MyModelWithObjectInData Class File
{
//add values to list
});
}
//Performing 3 Operations to Add Data in Different List (All 3 are Selecting the Values from Different Tables) as i need to Give Response with 3 Different List.
return p = JObject.FromObject(new
{
batchData = serviceResponseModel.GetBatchDataModel,
otherdata1 = serviceResponseModel.otherdata1, //list Residing in my MyModelWithObjectInData Model
otherdata2 = serviceResponseModel.otherdata2 //list Residing in my MyModelWithObjectInData Model
});
I have used below code to track all the request coming through the Service but i am getting error within this.
//here i am getting error
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
//var content = request.Content.ReadAsStringAsync().Result;
ServiceTypes myObjs = JsonConvert.DeserializeObject<ServiceTypes>(request.Content.ReadAsStringAsync().Result);
bool result = _serviceLogService.InsertLogs(request, myObjs); //Getting Error while inserting data here on multiple request
return base.SendAsync(request, cancellationToken).ContinueWith((task) =>
{
HttpResponseMessage response = task.Result;
return response;
});
}
Any Help in this would be Much Appreciated.
Providing a code snippet of your data calls from your api service would be most handy. Their are a few ways to handle concurrent queries; if you have not explored it yet the async and await method would be best to leverage in this scenario. But this is from a vague stand point I would need to look at your code to provide you a definitive answer.
Additional information "If you're new to asynchronous programming or do not understand how an async method uses the await keyword to do potentially long-running work without blocking the caller’s thread, you should read the introduction in Asynchronous Programming with Async and Await (C# and Visual Basic)."
Hopefully, this information puts you on the right track.
Best.
I found the Solution using Async Method to Handled Recurring Request of Services
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
//Logic to Track all the Request
var response = await base.SendAsync(request, cancellationToken);
return response;
}
My Controller in not able to initiate the context file as request it's already initiated and "concurrent requests" that dead locking my objects because i am getting multiple request from the devices so that i have modified he code to Logs all the Service within SendAsync Method and await helps me to wait until the task completed.