await not completing in extension method but working in controller - asp.net

I am using async and await in a controller.
The following code works fine
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create(CompetitionViewModel viewModel)
{
if (ModelState.IsValid)
{
User user = null;
using (var facebookClient = new FacebookClient(viewModel.AccessToken))
{
var facebookUser = await facebookClient.Me();
user = entityStorage.GetUser(facebookUser);
FormsAuthentication.SetAuthCookie(user.FacebookId, true);
}
However if I try and execute the same code in an extension method then the await never completes.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CompetitionViewModel viewModel)
{
if (ModelState.IsValid)
{
var user = entityStorage.GetCurrentUser(viewModel.AccessToken).Result;
and
public static class Helpers
{
public async static Task<User> GetCurrentUser(this IEntityStorage entityStorage, string accessToken)
{
User user = null;
using (var facebookClient = new FacebookClient(accessToken))
{
var facebookUser = await facebookClient.Me(); //STUCK HERE!!
user = entityStorage.GetUser(facebookUser);
FormsAuthentication.SetAuthCookie(user.FacebookId, true);
}
return user;
}
I am using MVC4 and have <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" /> set in my web.config as per other threads suggestions.
Can anyone tell me why this is and how I can get it to work?

I have a blog post that covers this in detail.
In short, you are causing a deadlock by calling Result. Instead, make your Create method async and use await to get the user.:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create(CompetitionViewModel viewModel)
{
if (ModelState.IsValid)
{
var user = await entityStorage.GetCurrentUser(viewModel.AccessToken);

Related

Asp.net core attribute route issue

I have this code:
[Route("Users")]
public class UserRegistrationController : Controller
{
[HttpGet("details/{userId}")]
public async Task<IActionResult> UserDetails(Guid userId)
{
// .....
}
[HttpPost("Save")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SaveUser(UserVM userVM)
{
// ......
return RedirectToAction("details", "Users", new { userId = userVM.UserId });
}
}
If I save the user redirectToAction generate userId as query string, this create an issue.
I mean instead of
https://localhost:5001/Users/Details/5de304c7-4c69-4819-c879-08d90306b555
redirect to action creates the URL as
https://localhost:5001/Users/Details?userId=5de304c7-4c69-4819-c879-08d90306b555
which causes a 404 error.
How do I solve this issue? I want to pass userId in route as below
https://localhost:5001/Users/Details/5de304c7-4c69-4819-c879-08d90306b555
Thanks in advance.
The issue was, the action method UserDetails need to add route [Route("details")] This will solve the issue.
[Route("Users")]
public class UserRegistrationController : Controller
{
[HttpGet("details/{userId}")]
[Route("details")]
public async Task<IActionResult> UserDetails(Guid userId)
{
// .....
}
[HttpPost("Save")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SaveUser(UserVM userVM)
{
// ......
return RedirectToAction("details", "Users", new { userId = userVM.UserId });
}
}

Asp.NET Core FindByNameAsync Can't Find A User

I use ASP.NET Core Identity and try "FindByNameAsync" method to get a user with username. I am sure there is a user with the username. But "FindByNameAsync" can't find the user. "GetByUserName" is my method and it queries db with username and it found the user as i expect. Has FindByNameAsync a bug or?
//this is can't find the user with username
var appUser = await UserService.FindByNameAsync(userName);
//this is my method and it works well
var appUser = UserService.GetByUserName(userName);
I'm normally using UserManager to find the info about the user
private readonly UserManager<User> _userManager;
public async Task<User> GetUserAsync(ClaimsPrincipal principal)
{
return await _userManager.GetUserAsync(principal);
}
public async Task<User> FindByNameAsync(string username)
{
return await _userManager.FindByNameAsync(username);
}
public async Task<User> FindByEmailAsync(string email)
{
return await _userManager.FindByEmailAsync(email);
}
public async Task<User> FindByIdAsync(string id)
{
return await _userManager.FindByIdAsync(id);
}
You can view my full source code here

.NET Core API + MVC Core Client supported by RestSharp

I have created .NET Core Web Api with JWT authentication. Now, I am in the middle of creating web app using MVC Core. In MVC project I have API client wrapper:
Interface:
public interface IWebApiService
{
Task<T> AuthenticateAsync<T>(string userName);
Task<T> GetAsync<T>(string action, string authToken);
Task PutAsync<T>(string action, T data, string authToken);
Task PostAsync<T>(string action, T data, string authToken);
}
Implementation:
public class WebApiService : IWebApiService
{
private readonly WebApiSettings _webApiSettings;
public WebApiService(WebApiSettings webApiSettings)
{
_webApiSettings = webApiSettings;
}
public async Task<T> AuthenticateAsync<T>(string userName)
{
var client = new RestClient(_webApiSettings.BaseUri);
var request = new RestRequest("/Login", Method.POST)
{
RequestFormat = DataFormat.Json
};
request.AddBody(new { UserName = userName });
var response = await client.ExecuteTaskAsync(request);
if (response.IsSuccessful)
{
return JsonConvert.DeserializeObject<T>(response.Content);
}
throw new ApiException(response.StatusCode.ToString(), response.ErrorMessage);
}
public async Task<T> GetAsync<T>(string action, string authToken)
{
var client = new RestClient(_webApiSettings.BaseUri);
var request = new RestRequest(action, Method.GET)
{
RequestFormat = DataFormat.Json
};
request.AddHeader("Authorization", $"Bearer {authToken}");
var response = await client.ExecuteTaskAsync(request);
if (response.IsSuccessful)
{
return JsonConvert.DeserializeObject<T>(response.Content);
}
throw new ApiException(response.StatusCode.ToString(), response.ErrorMessage);
}
public Task PutAsync<T>(string action, T data, string authToken)
{
// TODO
throw new NotImplementedException();
}
public Task PostAsync<T>(string action, T data, string authToken)
{
// TODO
throw new NotImplementedException();
}
}
MVC Login Controller:
public class LoginController : Controller
{
private readonly IWebApiService _webApiService;
public LoginController(IWebApiService webApiService)
{
_webApiService = webApiService;
}
public async Task<IActionResult> Get(string redirectUrl)
{
var user = User.Identity.Name;
if(user == null)
throw new WebInterfaceException("Invalid username.");
var response = await _webApiService.AuthenticateAsync<JwtToken>(user);
HttpContext.Session.SetObjectAsJson("Token", response);
return Redirect(redirectUrl ?? "/Home/Index");
}
}
I keep JWT object in session as I didn't find better solution for storing tokens in MVC Core.
Below example controller:
public class ExampleController : Controller
{
private readonly IWebApiService _webApiService;
public ExampleController(IWebApiService webApiService)
{
_webApiService = webApiService;
}
[HttpGet]
public async Task<IActionResult> Browse()
{
var jwtToken = HttpContext.Session.GetObjectFromJson<JwtToken>("Token");
if (jwtToken == null)
{
return RedirectToAction("Get", "Login", new { redirectUrl = Request.Path});
}
var response = await _webApiService.GetAsync<IEnumerable<ExampleBrowseViewModel>>("/Examples", jwtToken.Token);
return Json(response);
}
}
My problem is that in every controller action I will have to check if token is not null. If it's null, I am redirecting to Login page where I am retrieving token from API and redirecting to originally requested page. I would like to have some token handler where so I will not repeat the same code over and over. Additionally in my JWT object I have token expiration time and I would like to refresh it once it will expire so user could continue sending requests to API.
Can you give me few advises so I could accomplish this?

using ASP.Net builtin web API in MVC Project

i am using WebAPI MVC.Net project which has built in Web API codes.
For registration, it has function in Accounts Controller
// POST api/Account/Register
[AllowAnonymous]
[Route("Register")]
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
IdentityUser user = new IdentityUser
{
UserName = model.UserName
};
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
IHttpActionResult errorResult = GetErrorResult(result);
if (errorResult != null)
{
return errorResult;
}
return Ok();
}
my API link for registration is epolleasy.azurewebsites.net/api/Account/Register which is giving error. I am unable to find the reason for this error.

Request from desktop client hits the wrong controller action

From a Windows Forms desktop application, I make a request to an end-point in my ASP.NET MVC Web Application (not Web API).
The request is made for the endpoint "~/Account/APILogin" but it hits the "~/Account/Login" (GET) action even when I am making a post request to the former. Why is that so?
Here are the relevant bits of code:
In the ASP.NET MVC Application
class AccountController : Controller
{
// The request comes to this guy
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
// get request for Web browser clients
ViewBag.ReturnUrl = returnUrl;
return View();
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
// post request for Web browser clients
}
// It should really have hit this
[HttpPost]
public JsonResult APILogin(LoginRequest loginRequest)
{
if (loginRequest == null || string.IsNullOrEmpty(loginRequest.UserName) || string.IsNullOrEmpty(loginRequest.Password))
{
return Json(LoginResult.CreateFailure("Invalid login. Please try again."));
}
var hashedPassword = UserManager.PasswordHasher.HashPassword(loginRequest.Password);
var user = BusinessManager.GetUser(loginRequest.UserName, hashedPassword);
if (user == null)
{
return Json(LoginResult.CreateFailure("Invalid login. Please try again."));
}
return Json(LoginResult.CreateSuccess());
}
}
In the Global.asax of the ASP.NET MVC Web Application
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
ValueProviderFactories.Factories.Add(new JsonValueProviderFactory());
}
}
In the Windows Forms desktop client application
private async void btnOk_Click(object sender, EventArgs e)
{
var client = new Client();
var loginRequest = new LoginRequest { UserName = txtUserName.Text.Trim(), Password = txtPassword.Text.Trim() };
var loginResult = await client.LoginAsync(loginRequest);
if (loginResult.Succeeded)
{
Close();
}
else
{
ReportInvalidLogin(loginResult.FailureMessage);
}
}
In a class library that makes the HTTP Request on behalf of clients to the ASP.NET Web Web Application
class Client : IDisposable
{
private WebClient _webClient = null;
private string _baseUrl = null;
public Client()
{
_webClient = new WebClient();
_baseUrl = ConfigurationManager.AppSettings["WebApplicationBasePath"];
if (string.IsNullOrEmpty(_baseUrl))
{
throw new ArgumentNullException("Please add a key named WebApplicationBasePath to the configuration file with the base Url of the server web application.");
}
}
public async Task<LoginResult> LoginAsync(LoginRequest loginRequest)
{
var loginUrl = string.Format($"{_baseUrl}Account/APILogin");
var data = await Task.Factory.StartNew<string>(() => JsonConvert.SerializeObject(loginRequest));
_webClient.Headers.Add(HttpRequestHeader.ContentType, "application/json");
var responseString = await _webClient.UploadStringTaskAsync(loginUrl, "POST", data);
var loginResult = await Task.Factory.StartNew<LoginResult>(() =>
JsonConvert.DeserializeObject<LoginResult>(responseString));
return loginResult;
}
}
I do not have an AuthorizeAttribute applied yet, so that, too, as a possible cause is out of the question.
Somewhere in your stack (possibly IIS settings, web.config, or applying a global filter) authentication is being required for all requests, unless explicitly marked with the [AllowAnonymous] attribute. The solution is simple, add that attribute:
[HttpPost]
[AllowAnonymous]
public JsonResult APILogin(LoginRequest loginRequest)

Resources