I have an ASP.NET Core 2.0 application and I'm trying to attach a user to a model:
var user = _userManager.FindByIdAsync(Model.Author);
var promotion = new Promotion()
{
Title = Model.Title,
User = user //error here,
Created = DateTime.Now
};
The problem with this code is that I can't assign user to promotion.User as user is the result of an async operation. I'd prefer not to use FindByIdAsync but for some reason I can't find FindById.
UserManager contains only async API and FindByIdAsync actually returns Task<User> instead of User. So you need to make your code async also and use FindByIdAsync like this:
var user = await _userManager.FindByIdAsync(Model.Author); // will return the User
Only if it is not possible leave your code synchronous, e.g. by calling Result property of the Task which will cause your thread to block until the result is available
var user = _userManager.FindByIdAsync(Model.Author).Result;
Related
I'm working on an app and I'm relatively new to this scene, but I'm running into some troubles when I'm looking to simply query some data.
I'm calling a function after someone logs in. The login function only sends the necessary information and I want to do a secondary ping to my server to get a little more user information, more than just verifying the login info.
getUser (userid) async{
List<Map<String, dynamic>> user=[] ;
var client = new http.Client();
try {
var req = await client.post(urlPath+'mobileGetUser', body: {'thisUserID': userid'});
var jsonResponse = convert.jsonDecode(req.body);
//print('Here: '+jsonResponse.toString());
var id = jsonResponse['id'] ?? '';
var joinDate = jsonResponse['joinDate'] ?? '';
var userEmail = jsonResponse['userEmail'] ?? '';
var displayName = jsonResponse['displayName'] ?? '';
var timezone = jsonResponse['timezone'] ?? '';
var verified = jsonResponse['verified'] ?? '';
user = [{'id': id}];
user = [{'joinDate': joinDate}];
user = [{'userEmail': userEmail}];
user = [{'displayName': displayName}];
user = [{'timezone': timezone}];
user = [{'verified': verified}];
return user
} finally {
client.close();
}
}
I'm used to working in PHP. In that language, I'd pass the JSON object or associative array back to the calling function and accessing the individual fields would be as simple as
$displayName = $user['displayName'];
But this isn't PHP. List types are a little strange to me still.
From my calling function, I try to test this with:
thisUser = getUser(userid);
print('Successful Login: Display name: '+thisUser.toString());
And I get a message of:
Successful Login: Display name: Instance of 'Future<dynamic>'
How can I access this data? I've tried a few ways to get it. Also, is there a better way to create my List? I'm definitely going through a few steps that feel unnecessary. I'm honestly like to just pass it the entire JSONresponse. I'm just a bit out of my depth.
You could do this:
thisUser = await getUser(userid);
print('after'); // this will print after getUser() finishes
If you don't await for getUser() to finish, then you'll receive a Future.
Another way would be to use that Future and add a listener to it, like this:
getUser(userid).then((value) {
print('after'); // this will print after getUser() finishes
thisUser = value;
});
print('before'); // this will print before getUser() finishes
This is assuming you aren't using this on widgets. Otherwise you could use FutureBuilder to detect when the Future finishes and show something.
I have a webservice .Net core2 that has certain methods that send an email. I have it working fine using smtpclient.sendemailasync.
public async Task<bool> SendEmailAsync(MailMessage email)
{
try
{
if (string.IsNullOrWhiteSpace(emailFrom)) email.From = new MailAddress(emailFrom);
using (SmtpClient client = getSMTPClientInstance())
{
await client.SendMailAsync(email);
}
return true;
}
catch (Exception ex)
{
Log.Error(ex, "Error sending email in EmailService.SendEmailAsync");
return false;
}
}
The only issue is that some SMTP servers take a little too long to respond. I want to set up the email, queue it and return without waiting for the result.
Just using an unawaited async is out for 2 reasons;
It is not reliable to continue a method outside a request context in asp
I need access to the database context of my entity framework to write a log
I have to allow for external or internal SMTP (my client specifies), so a collection folder is not a possibility - at least not without a service that manages it.
How could I achieve this? Do I need to write a service that manages this? If so, how would I do that inside my .Net Core App, keeping in mind that the service also needs to access the EF context to write a log
UPDATE
There is plumbing available in .NetCore DI especially for this. Refer to my additional answer below. Use IServiceScopeFactory
You can call the RegisterAsyncTask method on the Page object. That will signal the ASP.NET runtime you want to make sure these are finished before terminating the request context:
Example:
public void Page_Load(object sender, EventArgs e)
{
RegisterAsyncTask(new PageAsyncTask(LoadSomeData));
}
public async Task LoadSomeData()
{
var clientcontacts = Client.DownloadStringTaskAsync("api/contacts");
var clienttemperature = Client.DownloadStringTaskAsync("api/temperature");
var clientlocation = Client.DownloadStringTaskAsync("api/location");
await Task.WhenAll(clientcontacts, clienttemperature, clientlocation);
var contacts = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Contact>>(await clientcontacts);
var location = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(await clientlocation);
var temperature = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(await clienttemperature);
listcontacts.DataSource = contacts;
listcontacts.DataBind();
Temparature.Text = temperature;
Location.Text = location;
}
https://www.hanselman.com/blog/TheMagicOfUsingAsynchronousMethodsInASPNET45PlusAnImportantGotcha.aspx
So, while I have marked an answer, there are a couple of options that are better solutions for my specific example. First is the option to use a library like hangfire to schedule tasks - although that is not technically an answer to the question.
The better solution in .net core is to use IServiceScopeFactory
With IServiceScopeFactory you can rescope a task so it doesnt go out of scope when the request is complete. I did the following directly in a controller (I later moved to using the hangfire approach, but this works). As you can see, the async task is fired off in a new unawaited thread while the controller code continues.
var task = Task.Run(async () =>
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var service = scope.ServiceProvider.GetRequiredService<ApprovalService>();
await service.sendResponseEmailAsync(approvalInfo.ApprovalId, userID, approvalInfo.emailTo, approvalInfo.ccTo);
}
});
I have installed the IS3/MR/IDM combination and everything is working fine. What I need to do now is make the the logged in user (ID, Name etc) available to all my MVC controllers so the obvious choice is to create a base controller so all others controllers inherit from it.
Could anyone advise if this is the best way to achieve this and perhaps provide some sample code?
Assuming you are already successfully authenticating against Identity Server 3, you should be all set already. If you look in the CallApiController you'll find this method
// GET: CallApi/UserCredentials
public async Task<ActionResult> UserCredentials()
{
var user = User as ClaimsPrincipal;
var token = user.FindFirst("access_token").Value;
var result = await CallApi(token);
ViewBag.Json = result;
return View("ShowApiResult");
}
the user variable should already contain claims for the user's name, Id and such. So
var id = user.FindFirst(Constants.ClaimTypes.Subject).Value;
var firstName = user.FindFirst(Constants.ClaimTypes.GivenName).Value;
var middleName = user.FindFirst(Constants.ClaimTypes.MiddleName).Value;
var lastName = user.FindFirst(Constants.ClaimTypes.LastName).Value;
Of course, that all assumes that you've got that information in your store of user information and I'm not checking for the errors that will occur if they are not there.
I am using Azure Mobile Services. I have a TableController<Photo>. In the controller, I can retrieve a single photo by id successfully. No problems using the following method:
//works
public SingleResult<Photo> GetPhoto(string id)
{
return Lookup(id);
}
However, since the photo is stored in Azure storage as a private blob, I want to tack on the SAS (Shared access signature) to allow my mobile client direct read access to the Azure blob for a given period of time.
In the GetPhoto call, I am successfully retrieving the SAS using the CloudBlobClient (removed for brevity).
I have defined a property on Photo called SasQueryString. I want to set it on the <Photo> object retrieved using Lookup(id) but the data returned from Lookup(id) is an IQueryable, not my strongly typed Photo object.
//! INCORRECT ! -- because photoResult is IQueryable
public SingleResult<Photo> GetPhoto(string id)
{
SingleResult<Photo> photoResult = Lookup(id);
//SingleResult<Photo> does not contain SasQueryString
photoResult.SasQueryString = "SAS from CloudBlobClient";
return photoResult;
}
If I do this, I can set the SasQueryString:
Photo photoResult = (Photo)Lookup(id).Queryable.FirstOrDefault<Photo>();
photoResult.SasQueryString = "SAS from CloudBlobClient";
However, I'm not sure how to return this strongly typed object as a SingleResult<Photo>.
//! INCORRECT ! -- this doesn't work because the Create method expects an IQueryable
return SingleResult<Photo>.Create(photoResult);
I've also tried this but photoResult is anIQueryable so I can't set the strongly typed SasQueryString value this way either.
//! INCORRECT !
var photoResult = Lookup(id).Queryable.Select(x => new Photo()
{
Id = x.Id,
TheOtherFields = x.TheOtherFields
});
photoResult.SasQueryString = "SAS from CloudBlobClient";
I am obviously missing something crucial here but it seems like I should be able to combine the lookup for the photo and the request for the SAS into a single call that returns my photo data after tacking on the SAS ticket...
== UPDATE ==
I found the following example: Creating a Leaderboard App with Azure Mobile Services .NET Backend. It is doing something similar to what I want to do but I have yet to try it.
// GET tables/PlayerRank/48D68C86-6EA6-4C25-AA33-223FC9A27959
public SingleResult<PlayerRankDto> GetPlayerRank(string id)
{
var result = Lookup(id).Queryable.Select(x => new PlayerRankDto()
{
Id = x.Id,
PlayerName = x.Player.Name,
Score = x.Score,
Rank = x.Rank
});
return SingleResult<PlayerRankDto>.Create(result);
}
which modified for my situation might look like the following:
public SingleResult<Photo> GetPhoto(string id)
{
var result = Lookup(id).Queryable.Select(x => new Photo()
{
Id = x.Id,
ImageUri = x.ImageUri,
SasQueryString = GetSas(id),
});
return SingleResult<PlayerRankDto>.Create(result);
}
You are not doing it the right way:
When you get the a list of Photos or a Photo it will give the data from storage in database and SasQueryString is not storaged, only the Url from blob storage should be;
You only provide SasQueryString in Insert or Update methods, because you need to define the url or update the url if need;
Note: Get methods do not change data
When a client app insert a photo the backend should do:
create the url for the photo and generate the SasQueryString
save the photo, with the url create, in database
before return the photo set the SasQueryString
client app upload the image to blob using the SasQueryString and url you provided
Why you have a Photo and a Controller for Photo???
If you have an object "Car" that has an image, it should have "car.Url" and a class similar to BlobItem.cs and you can see BlobStorageExtensions.cs.
Note: BlobItem.cs will be a not mapped property, I do not want save it on database.
I need to create a sample with it and the nugets...
I have created my data layer with EF 6 code first and I am populating the db through Seed method of EvInitializer class inheriting from DropCreateDatabaseIfModelChanges. The implementation of Seed method is
protected override void Seed(EvContext context)
{
//Add other entities using context methods
ApplicationUserManager manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context));
var user = new ApplicationUser { Email = "admin#myemail.com" ,UserName = "admin#myemail.com"};
var result = await manager.CreateAsync(user, "Temp_123");//this line gives error. obviously await cannot be used in non- async method and I cannot make Seed async
}
My question is how I can add a user in Seed method using UserManager class. when I change
var result = awit manager.CreateAsync(user, "Temp_123");
to
var result = manager.CreateAsync(user, "Temp_123").Result; //or .Wait
the application hangs indefinitely
In asp.net-identity-2 usermanager has non async methods to create.
var user = new ApplicationUser { Email = "admin#myemail.com", UserName = "admin#myemail.com" };
manager.Create(user, "Temp_123");
Same for rolemanager if you want to create "admin" role.
var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(context));
roleManager.Create(new Role("admin"));
make the user admin
manager.AddToRole(user.Id, "admin");
Edit: As trailmax commented, Create() extension method comes in with Microsoft.AspNet.Identity namespace so do not forget using Microsoft.AspNet.Identity
TMG is correct - there are non-async methods available, and that's the easiest way in this particular case.
In general, however - when you you only have an async version of a function available to you, and you can't change the implementation of the method to be Async - you can create a task and wait for it synchronously.
So - instead of:
IdentityResult result = await manager.CreateAsync(user, "Temp_123");
You can code:
Task<IdentityResult> createTask = manager.CreateAsync(user, "Temp_123");
createTask.Wait();
Once the Wait has finished, the IdentityResult gets returned in
createTask.Result
You can also set a timeout on the Wait, like this:
Task<IdentityResult> createTask = manager.CreateAsync(user, "Temp_123");
if (!createTask.Wait(5000)) // Wait up to 5 seconds
{
// We've timed out waiting - Do some error handling
}
else if (!createTask.Result.Succeeded)
{
// Creating the user failed - Do some error handling
}