So I've started my proof-of-concept for converting my nHibernate website to using Dapper.
My action method that I'm working appears to be working right now:
public IActionResult About()
{
ViewData["Message"] = "Your application description page.";
var invoice = invoiceRepo.GetInvoiceAsync(19992031);
var allInvoices = invoiceRepo.GetAllInvoicesAsync();
var model = new Models.AboutModel
{
Invoice = invoice.Result,
AllInvoices = allInvoices.Result
};
return View(model);
}
But then I realized/remembered, that in order for it to be asynchronous, I need to have Task on the action, like this:
public Task<IActionResult> About()
{
ViewData["Message"] = "Your application description page.";
var invoice = invoiceRepo.GetInvoiceAsync(19992031);
var allInvoices = invoiceRepo.GetAllInvoicesAsync();
var model = new Models.AboutModel
{
Invoice = invoice.Result,
AllInvoices = allInvoices.Result
};
return View(model);
}
But as soon as I do that, it tells me that I need to await something. In all of the examples I've seen, it just shows something like this:
var result = await repo.Get(param);
But I'm already doing the "awaiting" in my repos.
public async Task<Invoice> GetInvoiceAsync(int invoiceId)
{
const string query = "select InvoiceId, Name [InvoiceName] from dbo.Invoice where InvoiceId = #invoiceId";
using (var conn = GetConnection())
{
var dp = new DynamicParameters();
dp.Add("#invoiceId", invoiceId);
await conn.OpenAsync();
var invoiceResult = await conn.QueryAsync<Invoice>(query, dp, null, 30, CommandType.Text);
var invoice = invoiceResult.SingleOrDefault();
return invoice;
}
}
public async Task<List<Invoice>> GetAllInvoicesAsync()
{
const string query = "select InvoiceId, Name [InvoiceName] from dbo.Invoice where SalesPeriodId >= 17";
using (var conn = GetConnection())
{
await conn.OpenAsync();
var invoiceResult = await conn.QueryAsync<Invoice>(query, null, null, 30, CommandType.Text);
var invoices = invoiceResult.Take(1000).ToList();
return invoices;
}
}
So the whole point in me switching to asynchronous is to be able to do both of my calls asynchronously, then merge the results together when returning.
How do I change my controller action to do this asynchronously, while having the Task<IActionResult>? Like this:
public Task<IActionResult>About() {}
Update: is this correct then?
public async Task<IActionResult> About()
{
ViewData["Message"] = "Your application description page.";
var invoice = invoiceRepo.GetInvoiceAsync(19992031);
var allInvoices = invoiceRepo.GetAllInvoicesAsync();
var model = new Models.AboutModel
{
Invoice = await invoice,
AllInvoices = await allInvoices
};
return View(model);
}
Will this do both repo calls asynchronously (in parallel)?
You need to await in your controller too. Rule of thumb: Never say .Result, instead say await.
You should also declare your action method as public async as well.
Update: That would be the correct way to asynchronously call your repositories. The database calls should happen in parallel because both tasks are started before anything is awaited. You can always see this yourself by putting debug logging at the start and end of your DB methods and seeing that you get "start 1 start 2 end 1 end 2" or something similar instead of "start 1 end 1 start 2 end 2" if your queries are reasonably slow.
Related
I have a problem with retrieving data from an array for a specific user - there are many users in the array, I want to retrieve data for one specified in label1.Text
So - the first:
public async Task load_data()
{
var doing = await client.GetAllAsync(label1.Text);
foreach (var dane in doing )
{
lbItems_1.Items.Add(dane.Name);
lbItems_2.Items.Add(dane.Age);
lbItems_3.Items.Add(dane.Data);
}
}
Next : GetAllAsync()
public async Task<IEnumerable<Table_test>> GetAllAsync(string user_name)
{
var response = await client.GetAsync(_baseUrl + "/api/user/" + user_name);
}
And the last class:
[HttpGet]
public async Task<IActionResult> Get(string user_name)
{
using (var c = new MySqlConnection(con_sql.MySQL))
{
var sql = #"SELECT * FROM table_test WHERE username = #username";
var query = c.Query<Models.Table_test >(sql, new { username = user_name}, commandTimeout: 30);
return Ok(query);
}
}
I think it passes the parameter well - however when I try to download data for the appropriate user I have: NotFound code
Why? Ideas?
Thank you.
You should be able to specify the route parameter on the endpoint in the HttpGet attribute:
[HttpGet("{user_name}")]
public async Task<IActionResult> Get(string user_name)
{
using (var c = new MySqlConnection(con_sql.MySQL))
{
var sql = #"SELECT * FROM table_test WHERE username = #username";
var query = c.Query<Models.Table_test >(sql, new { username = user_name}, commandTimeout: 30);
return Ok(query);
}
}
Another option would be to call the endpoint using a query parameter:
public async Task<IEnumerable<Table_test>> GetAllAsync(string user_name)
{
var response = await client.GetAsync(_baseUrl + "/api/user?user_name=" + user_name);
}
I'm using ASP.NET Core MVC 2.0 and I've got an API that calls a method which opens a DB connection and inserts values into a model.
I'm hitting the same method 7 times and I'm just passing different information into it the parameters to build out my model. I HAVE to hit it seven different times, no rewrite of code will prevent this.
Instead of creating a loop and calling that method I wish to make parallel calls to the db so the response is faster. I've found multiple articles explaining how to do this e.g. How to properly make asynchronous / parallel database calls but there's enough difference that I can't seem to get it to work.
Following is the my method hitting the DB:
public async Task<RS_Model> Get_Results(RequestS_Model values)
{
//Deleted a bunch of code to keep it simple
string strQS = #"EXEC Get param1,param2,etc";
using (SqlConnection conSQL = new SqlConnection(connectionString))
{
using (SqlCommand cmdSQL = new SqlCommand(strQS, conSQL))
{
conSQL.Open();
using (SqlDataReader dtrSQL = cmdSQL.ExecuteReader())
{
while (dtrSQL.Read())
{
Double.TryParse(dtrSQL["Value1"].ToString(), out dblVal1);
} //Ends While
} //end SQLDataReader
} //Ends cmdSQL
} //ends using
results.Price = dblVal1;
return results;
} //Ends Get Results
My IActionResult for the api is:
[HttpGet]
public async Task<IActionResult> Get([FromQuery] RequestS_Model values)
{
SV_Results Results = new SV_Results();
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
RS_Model model = new RS_Model();
model.dblr[0] = await Results.Get_Results(values);
values.Parm1 = 2;
model.dblr[1] = await Results.Get_Results(values);
values.Parm1 = 3;
model.dblr[2] = await Results.Get_Results(values);
values.Parm1 = 4;
model.dblr[3] = await Results.Get_Results(values);
values.Parm1 = 5;
model.dblr[4] = await Results.Get_Results(values);
values.Parm1 = 6;
model.dblr[5] = await Results.Get_Results(values);
values.Parm1 = 7;
model.dblr[6] = await Results.Get_Results(values);
//int[] results = await Task.WhenAll(new Task<int>[] { task1, task2 });
return new OkObjectResult(model);
} //IActionResults
I know that I've forced them into a synchronous call be doing what I am, but I can't seem to make the 7 calls asynchronous and then wait for them to be all done before I build my final model. The final response needs to be in Json, but I haven't even gotten that far yet.
Instead of this:
model.dblr[0] = await Results.Get_Results(values);
model.dblr[1] = await Results.Get_Results(values);
model.dblr[2] = await Results.Get_Results(values);
model.dblr[3] = await Results.Get_Results(values);
model.dblr[4] = await Results.Get_Results(values);
model.dblr[5] = await Results.Get_Results(values);
model.dblr[6] = await Results.Get_Results(values);
Create a list of tasks and await them as a group:
var tasks = Enumerable.Range(0,7).Select( i => Results.Get_Results(values) ).ToList();
await Task.WhenAll(tasks);
for (int i=0; i<7; i++) model.dblr[i] = tasks[i].Result;
I keep getting the following error when I am executing my HttpPost form a second time.
InvalidOperationException: A second operation started on this context before a previous operation completed.
Any instance members are not guaranteed to be thread safe.
My ApplicationDbContext is initialised in my controller as such:
public class AssetController : Controller
{
private readonly ApplicationDbContext _context;
public AssetController(
ApplicationDbContext context,)
{
_context = context;
}
And this is the function in the controller that handles the post and save:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Add(IFormFile file, AddAssetViewModel model)
{
if (ModelState.IsValid)
{
var currentUser = await _userManager.GetUserAsync(HttpContext.User);
var assetOwnership =
_context.AssetOwnership.SingleOrDefault(o => o.AssetOwnershipId == model.OwnershipId);
var origin = _context.Location.SingleOrDefault(l => l.LocationId == model.OriginId);
var currentLocation = _context.Location.SingleOrDefault(l => l.LocationId == model.CurrentLocationId);
var division = _context.Division.SingleOrDefault(d => d.DivisionId == model.DivisionId);
var normalAsset = model.NormalAsset == 2;
var uploadSavePath = Path.Combine(_hostingEnvironment.WebRootPath, "Uploads\\AssetPictures\\");
var trackingNumber = GetTrackingNumber(model.OwnershipId, model.DivisionId);
var asset = new Asset
{
TrackingNum = trackingNumber,
Owner = currentUser,
Ownership = assetOwnership,
CurrentLocation = currentLocation,
Origin = origin,
ModelName = model.ModelName,
SerialNum = model.SerialNum,
Division = division,
Desc = model.Desc,
HwOpt = model.HwOpt,
SwOpt = model.SwOpt,
Availability = model.Availability,
Remarks = model.Remarks,
ReadyToSell = model.ReadyToSell,
PurchaseDate = model.PurchaseDate,
PurchasePo = model.PurchasePo,
NormalAsset = normalAsset,
MaterialNumber = model.MaterialNum,
IsTagged = model.IsTagged,
PurchasePrice = model.PurchasePrice,
IsDamaged = model.IsDamaged,
LastCalDate = model.LastCalDate,
Firmware = model.Firmware,
EstimatedNextCalDate = model.EstimatedNextCalDate,
LicenceExpiry = model.LicenceExpiry
};
if (file != null)
{
var imageName = asset.TrackingNum + ".jpg";
if (file.Length > 0)
{
using (var fileStream =
new FileStream(Path.Combine(uploadSavePath, imageName), FileMode.Create))
{
await file.CopyToAsync(fileStream);
}
asset.AssetPicture = imageName;
}
}
_context.Asset.Add(asset);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(model);
}
}
When I am only submitting the form for the first time, everything goes fine, item is saved into the database properly. However, when I try to add a second item, I get the error.
Can anybody help me to fix this?
Error output is saying it fails at
Project.Controllers.AssetController+<Add>d__14.MoveNext() in AssetController.cs
+
await _context.SaveChangesAsync();
I finally fixed it. I forgot to make one of my helper method with async calls async and those calls await. So that messed up the whole thing.
I'm using latest Stripe.net version
await SubscriptionsFacade.SubscribeUserAsync(user, planId, taxPercent: taxPercent);
raises
[MissingMethodException: Method not found: 'Void Stripe.StripeCustomerCreateOptions.set_Card(Stripe.StripeCreditCardOptions)'.]
Has something changed? I updated to the latest version and now my Stripe.net app is broken. Did Stripe introduce a new way of creating cards?
Here's the full code:
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var userIP = GeoLocation.GetUserIP(Request).Split(':').First();
var user = new ApplicationUser
{
UserName = model.Email,
Email = model.Email,
RegistrationDate = DateTime.UtcNow,
LastLoginTime = DateTime.UtcNow,
IPAddress = userIP,
IPAddressCountry = GeoLocationHelper.GetCountryFromIP(userIP),
BillingAddress = new BillingAddress()
};
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
// Create Stripe user
var taxPercent = user.IPAddressCountry != null && EuropeanVat.Countries.ContainsKey(user.IPAddressCountry) ?
EuropeanVat.Countries[user.IPAddressCountry] : 0;
// if no plan set, default to professional
var planId = string.IsNullOrEmpty(model.SubscriptionPlan)
? "starter"
: model.SubscriptionPlan;
var customer = new StripeCustomerService();
var customerInfo = customer.Get(user.CustomerIdentifier);
await SubscriptionsFacade.SubscribeUserAsync(user, planId, taxPercent: taxPercent);
await UserManager.UpdateAsync(user);
await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
await UserManager.EmailService.SendWelcomeEmail(user.UserName, user.Email);
TempData["flash"] = new FlashSuccessViewModel("Congratulations! Your account has been created.");
return RedirectToAction("Index", "Notes");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
and SubscribeUserAsync:
https://github.com/pedropaf/saas-ecom/blob/1370ac169807e97ffb7414610d5be4de4a3cc9ae/SaasEcom.Core/Infrastructure/Facades/SubscriptionsFacade.cs
as far as I can tell SubscribeUserAsync is requiring a card with its call.
private async Task<Subscription> SubscribeUserAsync
(SaasEcomUser user, string planId, CreditCard creditCard, int trialInDays = 0, decimal taxPercent = 0)
or
public async Task<Subscription> SubscribeUserAsync
(SaasEcomUser user, string planId, decimal taxPercent = 0, CreditCard creditCard = null)
since you are subscribing a user it probably wants a credit card to go with it. I would add either a card via creating a token or via calling and existing one with
var customer = new StripeCustomerService();
var customerInfo = customer.Get(user.CustomerIdentifier);
//then store card with customerInfo.DefaultSourceId somewhere and use it
We are consuming a WebAPI from an MVC site. We did a proof of concept from a console application like this, consuming AddUser Method, which reguster a new user in our DB.
static void Main()
{
M_Users user = new M_Users();
var newUser = new M_Users()
{
Password = "123",
FirstName = "Andrew",
Email = "someAndrew#someplace.com",
AdminCode = 1,
IsDisabled = false,
CountryId = 48,
CityId = 149,
DepartmentId = 3,
CreationDate = DateTime.Now,
CreatorUserId = 1
};
var star = RunAsync(newUser);
star.Wait();
//THIS LINE IS REACHED ALWAYS
Console.WriteLine(star.Result.UserID);
Console.ReadLine();
}
static async Task<M_Users> AddUserAsync(M_Users newUser)
{
M_Users user = new M_Users();
string requestUri = "api/User/AddUser";
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:44873/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// HTTP POST
HttpResponseMessage response = await client.PostAsJsonAsync<M_Users>(requestUri, newUser);
if (response.IsSuccessStatusCode)
{
Uri newUserUrl = response.Headers.Location;
user = await response.Content.ReadAsAsync<M_Users>();
}
return user;
}
}
This AddUserAsync method is called exactly the same in both cases,console application and MVC application, and methods in the biz layer are also the same, and not depicted here for doesn´t seem relevant.
In Console it just worked. User is registered and console printed the id which the user were saved with in the BD. This is pretty much what we needed to proof. That from a sync method we can invoke an async method. Now we try the same from the controler of our site, like this.
public ActionResult Index()
{
User spUser = null;
var spContext = SharePointContextProvider.Current.GetSharePointContext(HttpContext);
var model = new LoginViewModel();
// here we invoke sharepoint context for getting user info to bild the object
M_Users new_user = new M_Users()
{
Password = "123",
FirstName = spUser.Title,
Email = spUser.LoginName.Split('|')[2].ToString(),
AdminCode = 1,
IsDisabled = false,
CountryId = 48,
CityId = 149,
DepartmentId = 3,
CreationDate = DateTime.Now,
CreatorUserId = 1
};
var returnedUser = AddUserAsync(new_user);
returnedUser.Wait(); //HERE IT STOPS RUNNING
//THIS LINE IS NEVER REACHED
ViewBag.UserId = returnedUser.Result.UserID;
return View(model);
}
What needs to be done for the execution to continue.
We also tried ContinueWith() and it runs, but the async method is not executed and nothing is recorded in the DB. More specific, the method is invoked but it seem to go over it and retunrs nothing. :(
This is pretty much what we needed to proof. That from a sync method we can invoke an async method.
That's the wrong lesson to learn. Console applications use a different threading framework than ASP.NET, and that's what's tripping you up.
I explain it in full on my blog; the gist of it is that the await captures a "context" and uses that to resume the async method. In a Console app, this "context" is the thread pool context, so the async method continues running on some arbitrary thread pool thread, and doesn't care that you've blocked a thread calling Wait. However, ASP.NET has a request context that only allows in one thread at a time, so the async method is attempting to resume within that request context but it can't because there's already a thread blocked in that context.
The best solution is to allow async to grow through the codebase:
public Task<ActionResult> Index()
{
User spUser = null;
var spContext = SharePointContextProvider.Current.GetSharePointContext(HttpContext);
var model = new LoginViewModel();
M_Users new_user = new M_Users()
{
Password = "123",
FirstName = spUser.Title,
Email = spUser.LoginName.Split('|')[2].ToString(),
AdminCode = 1,
IsDisabled = false,
CountryId = 48,
CityId = 149,
DepartmentId = 3,
CreationDate = DateTime.Now,
CreatorUserId = 1
};
var returnedUser = await AddUserAsync(new_user);
ViewBag.UserId = returnedUser.UserID;
return View(model);
}