Reputation: 551
I have MVC project with service like below:
namespace comp.Services
{
public class CompService
{
public HttpClient client = new HttpClient();
public CompService()
{
client.BaseAddress = new Uri("someapiurl");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
}
protected async Task<string> GetProductAsync(string path)
{
var resp = "nothing here";
HttpResponseMessage response = await client.GetAsync(path);
if (response.IsSuccessStatusCode)
{
resp = await response.Content.ReadAsStringAsync();
}
return resp;
}
public string GetProduct(string path)
{
return GetProductAsync(path).GetAwaiter().GetResult();
}
}
}
and actionResult to view:
namespace comp.Controllers
{
public class HomeController : Controller
{
public CompService compService;
public HomeController()
{
compService = new CompService();
}
public ActionResult About()
{
var timeServer = compService.GetProduct("/api/time");
ViewBag.timeServer = timeServer;
return View();
}
}
}
When in debuger I encounter this line:
HttpResponseMessage response = await client.GetAsync(path);
program exit from debuger and there is no respone in browser.
The same code written in console application works.
In VS output is message that response is succes:
Application Insights Telemetry (unconfigured): {"name":"Microsoft.ApplicationInsights.Dev.RemoteDependency","time":"2018-02-18T13:48:31","tags":{"ai.internal.sdkVersion":"rddf:2.2.0-738","ai.internal.nodeName":"DESKTOP-xxxxx","ai.cloud.roleInstance":"DESKTOP-xxxxx"},"data":{"baseType":"RemoteDependencyData","baseData":{"ver":2,"name":"/api/v1/time","id":"xxxxx=","data":"https://api.xxx.com/api/v1/time","duration":"00:00:01.3150000","resultCode":"200","success":true,"type":"Http","target":"xxx","properties":{"DeveloperMode":"true"}}}}
In browser console output:
[14:51:33 GMT+0100 (Central European Standard Time)] Browser Link: Failed to send message to browser link server:
Error: SignalR: Connection must be started before data can be sent. Call .start() before .send()
Thanks for help.
Upvotes: 2
Views: 1292
Reputation: 247333
You should really keep the code async all the way through in stead of trying to mix synchronous and asynchronous code
namespace comp.Services {
public class CompService {
static HttpClient client = new HttpClient();
static CompService() {
client.BaseAddress = new Uri("someapiurl");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
}
public async Task<string> GetProductAsync(string path) {
var resp = string.Empty;
using(var response = await client.GetAsync(path)) {
if (response.IsSuccessStatusCode) {
resp = await response.Content.ReadAsStringAsync();
}
}
return resp;
}
}
}
The controller action should also be made async
namespace comp.Controllers {
public class HomeController : Controller {
private CompService compService;
public HomeController() {
compService = new CompService();
}
public async Task<ActionResult> About() {
var timeServer = await compService.GetProductAsync("api/time");
ViewBag.timeServer = timeServer;
return View();
}
}
}
That said, the service should also be abstracted
public interface ICompService {
Task<string> GetProductAsync(string path)
}
public class CompService : ICompService {
//...code removed for brevity
}
and injected into the controller instead of creating it manually.
public class HomeController : Controller {
private ICompService compService;
public HomeController(ICompService compService) {
this.compService = compService;
}
public async Task<ActionResult> About() {
var timeServer = await compService.GetProductAsync("api/time");
ViewBag.timeServer = timeServer;
return View();
}
}
Reference Async/Await - Best Practices in Asynchronous Programming
Upvotes: 2
Reputation: 11514
What you've got is a deadlock. Unlike console apps, ASP.Net applications run in a Synchronization Context. That context is captured when you block with GetResult()
. Then, in GetProductAsync
, you await on the context that is blocked. It cannot resume until GetResult
is done which cannot resolve until the await is done.
@NKosi 's answer should resolve the problem, there is no reason for you to have any synchronous code.
You can hack your code to work by explicitly allowing your await to run on a different context. You should not do this in production, it is not a fix. It can fail if someone maintaining the CompService
is not careful.
To await against a different context change this:
var timeServer = await compService.GetProductAsync("api/time");
To this:
var timeServer = await compService.GetProductAsync("api/time").ConfigureAwait(false);
I mention this only to help you understand what is happening in your code. Don't "fix" it this way and move on.
Upvotes: 2