I am coding a MVC 5 internet application with entity framework 6 and have a question in regards to using the await keyword when using the .Where() clause.
Here is my code that works:
public async Task<Account> GetAccount(string userName)
{
if (Session[userName] == null)
{
Account account = db.accounts.Where(a => a.userName.Equals(userName)).FirstOrDefault();
if (account == null)
{
//log out
return null;
}
Session[userName] = account;
}
return Session[userName] as Account;
}
I am wanting to use the await keyword when retrieving the Account object as follows:
Account account = await db.accounts.Where(a => a.userName.Equals(userName)).FirstOrDefault();
Can the await keyword be used when using the .Where() clause?
Thanks in advance.
There is no WhereAsync() method provided by EF (obviously because it can't potentially block, since LINQ uses deferred-execution), but since you're performing a FirstOrDefault()
you could simply use the FirstOrDefaultAsync() method:
Account account = await db.accounts.FirstOrDefaultAsync(a => a.userName.Equals(userName));
See MSDN
The await keyword can only be used on methods that return "Task...", neither .Where nor .FirstOrDefault (Which is the last method in the chain, and thus would be the one the await keyword would apply to) return Task<IEnumerable<Account>>
In theory you could write you own extension method which simply wraps around the .Where and .FirstOrDefault methods.
Also, this question isn't exactly EF specific, but rather a 'pure' C# question.
public static class ExtensionMethods
{
public static async Task<IEnumerable<T>> WhereAsync<T>(this IEnumerable<T> source, Func<T, bool> selector)
{
return await Task.Run(() => source.Where(selector));
}
}
Although that would be kind of overkill imho.
You could just wrap your entire method in a Task, so your final code would be something like:
public async Task<Account> GetAccount(string userName)
{
return await Task.Run(() =>
{
if (Session[userName] == null)
{
Account account = db.accounts.Where(a => a.userName.Equals(userName)).FirstOrDefault();
if (account == null)
{
//log out
return null;
}
Session[userName] = account;
}
return Session[userName] as Account;
});
}
Related
I use the following code to update the claims of a user in my web application. However, to update the claims/cookie of this user, I want to force him to login again. So essentially I want to expire his cookie after I update the claims. Any idea how to do this?
await _signInManager.RefreshSignInAsync(user); is the first thing I tried, but fails because I'm updating the claims of another user (the one that is currently logged in) :)
All other examples I found are more or less the same as RefreshSignInAsync and do not deal with the fact that I'm updating the claims of another user.
public async Task<IActionResult> AddClaimPost(string id)
{
var user = _context.ApplicationUser
.SingleOrDefault(m => m.Id == id);
foreach(var item in Request.Form)
{
if (item.Key.Contains("Claim"))
{
if (item.Value.Contains("true"))
{
if (!User.HasClaim(item.Key, item.Key))
{
var result = await _userManager.AddClaimAsync(user, new Claim(item.Key, item.Key));
}
}
else
{
var result2 = await _userManager.RemoveClaimAsync(user, new Claim(item.Key, item.Key));
}
}
}
await _signInManager.RefreshSignInAsync(user);
return RedirectToAction("Overview");
}
After searching a few days I discovered that what I want is not possible. You cannot force logging the user out without putting the cookie timespan to 0
options.Cookies.ApplicationCookie.ExpireTimeSpan = 0;
In this case it will check the cookie every time the user makes a request. With the following code you can than force the user to login again:
await _userManager.UpdateSecurityStampAsync(user);
I don't recommend the 0 expire timespan approach.
If you have a redis server (or any other persistent data store that is performant) you can do something like:
await redis.StringSetAsync("refresh_login_" + user.Id, "1", null);
Then on every page load you will check this redis value and refresh the signin if the key is set for you:
Filters/BaseActionFilter.cs:
public class BaseActionFilter: IAsyncActionFilter, IAsyncPageFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{ // For classic controllers
await PerformPageTasks(context.HttpContext);
await next();
}
public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context,
PageHandlerExecutionDelegate next)
{ // For razor pages in Areas/
if ((context.HandlerInstance is PageModel page))
{
await PerformPageTasks(context.HttpContext);
}
await next.Invoke();
}
public async Task PerformPageTasks(HttpContext context)
{
var signinManager = context.RequestServices.GetService<SignInManager<MyWebUser>>();
if (signinManager.IsSignedIn(context.User))
{
var cache = context.RequestServices.GetService<IDistributedCache>();
var redis = (await ((RedisCache)cache).GetConnectionAsync()).GetDatabase();
var userManager = context.RequestServices.GetService<UserManager<MyWebUser>>();
var user = await userManager.GetUserAsync(context.User);
if ((await redis.StringGetAsync("refresh_login_" + user.Id)) == "1")
{
await redis.KeyDeleteAsync("refresh_login_" + user.Id);
// refresh the user
await signinManager.RefreshSignInAsync(user);
}
}
}
public async Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context)
{
await Task.CompletedTask;
}
}
Startup.cs:
services.AddMvc(o =>
{
...
o.Filters.Add(new BaseActionFilter());
}).AddHybridModelBinder();
If you only use traditional controllers or Areas/ razor pages then you can adapt the code accordingly.
Note this requires the user to make an additional page load before the claims are set, so for things like [Authorize] you would need to put this code earlier in the chain and I'm not sure exactly how to do that.
I'm just getting to grips with creating a new WebAPI2 project in ASP.NET. I'm trying to get the controller to return data from a DTO I have created rather than the raw object classes that EF created. I've been following a tutorial on Microsoft Docs and have got my method which returns all records to work using the DTO, but I can't figure out how to correctly modify the method which only returns the record with the ID matching the passed parameter using an asynchronous task, like the default method does.
The default method generated by Visual Studio looks like this:
[ResponseType(typeof(Post))]
public async Task<IHttpActionResult> GetPost(int id)
{
Post post = await db.Post.FindAsync(id);
if (post == null)
{
return NotFound();
}
return Ok(post);
}
and I've got my modified method looking like this:
[ResponseType(typeof(PostDTO))]
public async Task<IHttpActionResult> GetPost(int id)
{
var _post = from p in db.Post
where p.PostID == id
select new PostDTO()
{
PostID = p.PostID,
SubmitTime = p.SubmitTime,
SubmitUsername = p.SubmitUsername,
};
if (_post == null)
{
return NotFound();
}
return Ok(_post);
}
This methods works just fine, but as you can see, it doesn't make use of .NET's Await/Async feature to perform the query asynchronously. I'll be honest and admit that I don't actually know if this matters, but I feel like if the default method was asynchronous, so should mine be. I just can't work out where to insert the Async and Await keywords to make this work.
You can use this method as,
[ResponseType(typeof(PostDTO))]
public async Task<IHttpActionResult> GetPost(int id)
{
var _post = await (from p in db.Post
where p.PostID == id
select new PostDTO()
{
PostID = p.PostID,
SubmitTime = p.SubmitTime,
SubmitUsername = p.SubmitUsername,
}).ToListAsync();
if (_post == null)
{
return NotFound();
}
return Ok(_post);
}
From JavaScript client code I am creating the following data:
var employee = {
FirstName: "Rudolf",
Salary: 99
};
I then pass this through an Ajax call to an MVC Web API Controller Action:
using System.Web.Http.OData;
public async Task<IHttpActionResult> Patch([FromUri] int employeeId, [FromBody] Delta<Employee> employee)
{
await _employeeService.Patch(employeeId, employee);
return Ok();
}
This calls my service to update the database as follows:
public async Task Patch(int employeeId, Delta<Employee> employee)
{
using (var context = new DBEntities())
{
if (employee.TryGetPropertyValue("Salary", out object salary))
{
var ss = Convert.ToDouble(salary); // Always 0
}
if (employee.TryGetPropertyValue("FirstName", out object firstName))
{
var ss = Convert.ToString(firstName); // Expected value
}
var currentEmployee = await context.Employees
.FirstOrDefaultAsync(e => e.Id == employeeId);
if (currentEmployee == null)
return;
employee.Patch(currentEmployee);
await context.SaveChangesAsync();
}
}
Note: I missed out some of the details for brevity as the actual client-server call is working fine.
The code seems to work as expected, but the Salary property (the only none-string one) is always set to 0 (zero). So that field never get's updated.
Any ideas why the Salary is not being passed through?
Note: I use very similar client-server code for GET/POST/PUT/DELETE and they all work fine, so I believe it is related to the Delta<> part.
Yes, I encountered the same problem with int properties.
I solved the problem using SimplePatch (v1.0 is only 10KB).
Disclaimer: I'm the author of the project.
It is inspired to Microsoft.AspNet.WebApi.OData but SimplePatch has no dependencies.
How to use
Install the package using:
Install-Package SimplePatch
Your MVC Web API Controller Action becomes:
using SimplePatch;
public async Task<IHttpActionResult> Patch([FromUri] int employeeId, [FromBody] Delta<Employee> employee)
{
await _employeeService.Patch(employeeId, employee);
return Ok();
}
Then your Patch method becomes:
public async Task Patch(int employeeId, Delta<Employee> employee)
{
using (var context = new DBEntities())
{
if (employee.TryGetPropertyValue("Salary", out object salary))
{
var ss = Convert.ToDouble(salary);
}
if (employee.TryGetPropertyValue("FirstName", out object firstName))
{
var ss = Convert.ToString(firstName);
}
var currentEmployee = await context.Employees
.FirstOrDefaultAsync(e => e.Id == employeeId);
if (currentEmployee == null)
return;
employee.Patch(currentEmployee);
await context.SaveChangesAsync();
}
}
Also, SimplePatch gives you the ability to ignore some properties when calling the Patch method.
Global.asax or Startup.cs
DeltaConfig.Init((cfg) =>
{
cfg.ExcludeProperties<Employee>(x => x.YourPropertyName);
});
If Salary has type int then there is an issue with that type
The Json parser converts integers to 64 bit ones, which won't match the 32 bit int properties declared on the entity and thus get ignored by this method.
This problem is mentioned here
So, you can use long instead of int or using the OData media type formatters
See this
I'm building an ASP.Net Web API application and i have the following code...
public IHttpActionResult GetCustomers() {
var customers = context.Customers.ToList();
return Ok(customers);
}
I'm using the Ok() method to return customers because i'm using an IHttpActionResult return type.
Now if i have the following method
public void DeleteCustomer(int id) {
var customerInDb = context.Customers.SingleOrDefault(c => c.Id == id);
if (customerInDb == null) {
NotFound();
}
context.Customers.Remove(customerInDb);
context.SaveChanges();
}
Can I use NotFound() method here when the return type of my ActionMethod is void???
Void does not have a return type. So you can try to call NotFound(), but I'm not sure if this would even compile - Haven't tried. Why don't you just go with an out of the box IHttpActionResult?
public IHttpActionResult DeleteCustomer(int id)
{
var customerInDb = context.Customers.SingleOrDefault(c => c.Id == id);
if (customerInDb == null)
{
return NotFound();
}
context.Customers.Remove(customerInDb);
context.SaveChanges();
return Ok(customerInDb);
}
Using IHttpActionResult is the more elegant version. If the id is invalid, you can just safely exit your method and tell the calling client that something went wrong. If everything went well, you're just giving the client a thumbs-up. IF you return your deleted entity or just an empty Ok() should not matter at this point.
Using void may or may not delete the entity in your data storage. The client would never know, because the server would not return any response.
I am using WebAPI and I tried to scaffold a controller. This is the method it came up with for a fetch by ID:
// GET: api/ExamStatus/5
[ResponseType(typeof(ExamStatus))]
public async Task<IHttpActionResult> GetExamStatus(int id)
{
ExamStatus examStatus = await db.ExamStatus.FindAsync(id);
if (examStatus == null)
{
return NotFound();
}
return Ok(examStatus);
}
What if I wanted to do an async select of all the data from the ExamStatus entity? Can someone explain how I can do this?
You could try the .ToListAsync extension method:
[ResponseType(typeof(List<ExamStatus>))]
public async Task<IHttpActionResult> GetExamStatuses()
{
var result = await db.ExamStatus.ToListAsync();
return Ok(result);
}
Obviously this will select ALL the rows in your database and serialize them into the response.