AmbiguousActionException when confirm e-mail in identity - c#

I'm working on a web service to manage user registration..
I don't understand because obtain this:
AmbiguousActionException: Multiple actions matched. The following actions matched route data and had all constraints satisfied:
Test.Api.Controllers.AccountsController.ConfirmEmail (Test.Api)
Test.Api.Controllers.AccountsController.ResetPassword (Test.Api)
This is my code
[HttpPost]
[AllowAnonymous]
[Route("register")]
public async Task<IActionResult> Register([FromBody]RegistrationViewModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var userIdentity = _mapper.Map<AppUser>(model);
var result = await _userManager.CreateAsync(userIdentity, model.Password);
if (!result.Succeeded) return new BadRequestObjectResult(Errors.AddErrorsToModelState(result, ModelState));
var token = await _userManager.GenerateEmailConfirmationTokenAsync(userIdentity);
var callbackUrl = Url.EmailConfirmationLink(userIdentity.Id, token, Request.Scheme);
await _emailSender.SendEmailAsync(model.Email, "Confirm ", $"Please confirm your account by clicking this link: <a href='{callbackUrl}'>link</a>");
await _appDbContext.Customers.AddAsync(new Customer { IdentityId = userIdentity.Id, Location = model.Location });
await _appDbContext.SaveChangesAsync();
return new OkObjectResult("Account created");
}
I done a helper for buld the callbackUrl whit this code:
public static string EmailConfirmationLink(this IUrlHelper urlHelper, string userId, string token, string scheme)
{
return urlHelper.Action(
action: nameof(AccountsController.ConfirmEmail),
controller: "Accounts",
values: new { userId, token },
protocol: scheme);
}
public async Task<IActionResult> ConfirmEmail(string userId, string token)
{
if (userId == null || token == null)
{
return new BadRequestResult();
}
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{userId}'.");
}
var result = await _userManager.ConfirmEmailAsync(user, token);
if (!result.Succeeded)
{
return new BadRequestResult();
}
await _emailSender.SendEmailAsync(user.Email, "Account confermato","Il tuo account รจ stato confermato");
return new OkObjectResult("Account confirmed");
}
public IActionResult ResetPassword(string code = null)
{
if (code == null)
{
throw new ApplicationException("A code must be supplied for password reset.");
}
var model = new ResetPasswordViewModel { Code = code };
return View(model);
}
how is it possible that these two methods ( ConfirmEmail(string userId, string token) and ResetPassword(string code = null)) can create ambiguity? the two fime are different

You've indicated in the comments on your question that AccountsController is decorated with the following attribute:
[Route("api/[controller]")]
This means that your ConfirmEmail and ResetPassword actions are using the same route, which is simply api/Accounts in this case. When this route is loaded up in the browser, both actions are candidates for processing the request and so you get an error as described in your question.
In order to fix this, there are a few options:
Add [Route("ConfirmEmail")] to ConfirmEmail and [Route("ResetPassword")] to ResetPassword (similar to what you've done with the Register action).
Add [Route("[action]")] to both ConfirmEmail and ResetPassword, where [action] is a placeholder for the action name.
Remove [Route("register")] from your Register action and update the controller's attribute to [Route("api/[controller]/[action]")]. You'd also need to remove [Route(...)] from any other actions you may have. Note that this approach is only viable if all your actions are routed according to the api/[controller]/[action] pattern.

Related

How to pass Data from action to action using mvc and not in router

i have an action inside of users and i want that action to return the user to another action in another controller but not in the router parameter, Here is a sample
public IActionResult LoginCheck(UserForm user)
{
AuthUser auth = new AuthUser(_context);
var result = auth.IsLoggedIn(user.Email, user.Password);
if(result.isfound==false)
{
return NotFound();
}
result.User.IsAuth = true;
return RedirectToAction("Home","Index",result.User);
}
public async Task<IActionResult> Index(User user)
{
if(user.IsAuth == false)
{
return Unauthorized();
}
just part of the code
Home index did not use the incoming user as it was sent as router parameters i think
Welcome to stackoverflow!
You can use TempData to achieve that.
public IActionResult LoginCheck(UserForm user)
{
AuthUser auth = new AuthUser(_context);
var result = auth.IsLoggedIn(user.Email, user.Password);
if(result.isfound==false)
{
return NotFound();
}
result.User.IsAuth = true;
TempData["user"] = result.User;
return RedirectToAction("Home","Index");
}
Then you can get it in the other action
public async Task<IActionResult> Index()
{
if(TempData["user"] == null)
{
return Unauthorized();
}else{
var someUser= (User)TempData["user"];
}
}
But I do not recommend using TempData for sensitive data.
You can use second action as method:
public async Task<IActionResult> LoginCheck(UserForm user)
{
AuthUser auth = new AuthUser(_context);
var result = auth.IsLoggedIn(user.Email, user.Password);
if(result.isfound==false)
{
return NotFound();
}
result.User.IsAuth = true;
return await Index(result.User);
}
second action
[NonAction] // Indicates that a controller method is not an action method.
public async Task<IActionResult> Index(User user)
{
if(user == null)
{
return Unauthorized();
}
else{
var someUser= user;
}
}
Use redirection make browser to handle this transfer and it is slower.

How To Pass ErrorMessage From API to MVC In Asp.Net Core

I've created an API. When you face an error, It shows you the type of error with it's message. But When I try to use that API in my MVC project, It just shows the type of error. I want to see the message in Modelstate.AddModelError
Here is API controller for Login
[HttpPost("login")]
public async Task<IActionResult> LoginUser([FromBody] UserDtoLogin user)
{
var userToRetrieve = await _applicationDbContext.Users.FirstOrDefaultAsync(u => u.UserName == user.UserName);
if (userToRetrieve == null)
{
ModelState.AddModelError("username", "Such a user doesn't exists! Enter the correct username please");
return NotFound(ModelState);
}
if (!_userRepository.VerifyPasswordHash(user.Password, userToRetrieve.PasswordHash, userToRetrieve.PasswordSalt))
{
ModelState.AddModelError("password", "Wrong Password!");
return BadRequest(ModelState);
}
await _userRepository.Login(userToRetrieve);
return Ok(user);
}
Here is MVC Controller for Login
[HttpPost]
public async Task<IActionResult> Login(User user)
{
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost:42045/api/user/login");
if (user != null)
{
request.Content = new StringContent(JsonConvert.SerializeObject(user),
System.Text.Encoding.UTF8, "application/json");
}
var client = _clientFactory.CreateClient();
HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
ViewBag.StatusCode = System.Net.HttpStatusCode.OK;
var apiString = await response.Content.ReadAsStringAsync();
user = JsonConvert.DeserializeObject<User>(apiString);
}
else
{
ViewBag.StatusCode = response.StatusCode;
}
return View(user);
}
I write a simple demo to show how to pass ErrorMessage From API to MVC In Asp.Net Core, you can reference to it.
API
[Route("api/[controller]")]
[ApiController]
public class LoginController : ControllerBase
{
//For testing convenience, I use hard code here
List<UserDtoLogin> context = new List<UserDtoLogin>
{
new UserDtoLogin
{
UserName = "Mike"
},
new UserDtoLogin
{
UserName = "Jack"
},
new UserDtoLogin
{
UserName = "Lily"
}
};
[HttpPost("login")]
public IActionResult LoginUser([FromBody] UserDtoLogin user)
{
var userToRetrieve = context.FirstOrDefault(u => u.UserName == user.UserName);
if (userToRetrieve == null)
{
return BadRequest("Such a user doesn't exists! Enter the correct username please");
}
//your logic.....
return Ok();
}
}
MVC/Controller
[HttpPost]
public async Task<IActionResult> Privacy(UserDtoLogin todoItem)
{
var todoItemJson = new StringContent(JsonSerializer.Serialize(todoItem),Encoding.UTF8,Application.Json);
using var httpResponseMessage = await _httpClient.PostAsync("your api url", todoItemJson);
var errormessage = httpResponseMessage.Content.ReadAsStringAsync().Result;
return View(todoItem);
}
Then you can see it can receive the errormessage successfully.

UpdateAsync method is not work in web api?

I am create small demo for user registration using web api mvc c#.i am register Succeeded for user using 'Register' in web api.now i want to using this method also edit this user by id so how can do that i don't know any one know please let me know. i my code i will manage add/edit call in one method so i will first check the condition on id is null then add and id is not null then go for the edit but how can edit this record and role.
here this my method :
[Route("Register")]
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
try
{
if (model.Id == "")
{
//here is for add user method
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email,PhoneNumber = model.PhoneNumber };
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
var UsersContext = new ApplicationDbContext();
var res = UsersContext.Users.Where(x => x.UserName == user.UserName).FirstOrDefault();
var UserId = res.Id;
await UserManager.AddToRoleAsync(UserId, model.UserRole);
return Ok(result);
}
return Ok(result);
}
else
{
//here i want write edit code
var UsersContext = new ApplicationDbContext();
var Team = UsersContext.Users.Find(model.Id);
Team.UserName = model.UserName;
Team.Email = model.Email;
Team.PhoneNumber = model.PhoneNumber;
IdentityResult result = await UserManager.UpdateAsync(Team); //here getting error.
return Ok(result);
}
return Ok("Done");
}
catch (Exception ex)
{
}
return Ok();
}
UPDATED:
First check whether you are passing values to all required field or not.
Keep this UserStore after your controller class calling braces {.
[Authorize]
public class AccountController : Controller
{
UserStore<IdentityUser> myUserStore = new UserStore<IdentityUser>(new
ApplicationDbContext());
//rest code
Then inside your Register() Post method, do like this bellow,
if (model.Id == "")
{
//Add new user code
}
else
{
var context = myUserStore.Context as ApplicationDbContext;
var Team = context.Users.Find(model.Id);
//change the field value what ever you want to update
context.Users.Attach(Team);
context.Entry(Team).State = EntityState.Modified;
context.Configuration.ValidateOnSaveEnabled = false;
await context.SaveChangesAsync();
return Ok("Updated");
}
Hope it helps :)

return errors from web api 2 account register

I am trying to use the account controller in the MVC project to call the register account method in a WebApi 2 account controller. All works fine but I cant figure out how to return errors back to the MVC project such as: "password must contain Upper case and lower case" etc.
ASP.NET MVC account controller register:
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var response =
await
ApiRequest.PostAsync(String.Format("{0}/api/v1/account/register", "http://localhost:12345"), model);
if (response.IsSuccessStatusCode)
{
return RedirectToAction("Index", "Home");
}
// Add errors
}
// If we got this far, something failed, redisplay form
return View(model);
}
ApiRequest class:
public static class ApiRequest
{
public static async Task<HttpResponseMessage> PostAsync(string uri, object item)
{
StringContent content = new StringContent(await Json.SerializeAsync(item));
content.Headers.ContentType = new MediaTypeHeaderValue("text/json");
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
return await client.PostAsync(new Uri(uri), content);
}
}
public static async Task<HttpResponseMessage> GetAsync(string uri)
{
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
return await client.GetAsync(new Uri(uri));
}
}
public static async Task<HttpResponseMessage> PutAsync(string uri, object item)
{
StringContent content = new StringContent(await Json.SerializeAsync(item));
content.Headers.ContentType = new MediaTypeHeaderValue("text/json");
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
return await client.PutAsync(new Uri(uri), content);
}
}
public static async Task<HttpResponseMessage> DeleteAsync(string uri, object id)
{
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
return await client.DeleteAsync(new Uri(String.Format("{0}/{1}", uri, id)));
}
}
}
public static class HttpResponseMessageExtensions
{
public static async Task<T> DeserialiseContentAsync<T>(this HttpResponseMessage message)
where T : class
{
return await Json.DeserialiseAsync<T>(await message.Content.ReadAsStringAsync());
}
}
Web API 2 account controller register:
//
// POST: /Account/Register
[AllowAnonymous]
[Route("Register")]
[HttpPost]
public async Task<IHttpActionResult> Register(RegisterViewModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var user = new ApplicationUser
{
UserName = model.Username,
Email = model.Email,
FirstName = model.FirstName,
LastName = model.LastName
};
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
if (!result.Succeeded)
{
if (result.Errors != null)
{
foreach (string error in result.Errors)
{
ModelState.AddModelError("", error);
}
return BadRequest(ModelState);
}
return BadRequest();
}
// Send email verification
//string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
//var callbackUrl = new Uri(Url.Link("ConfirmEmail", new { userId = user.Id, code = code }));
//await
// UserManager.SendEmailAsync(user.Id, "Confirm your account",
// "Please confirm your account by clicking here");
Uri locationHeader = new Uri(Url.Link("GetUserById", new { id = user.Id }));
return Created(locationHeader, user);
}
GetErrorResult code:
private IHttpActionResult GetErrorResult(IdentityResult result)
{
if (result == null)
{
return InternalServerError();
}
if (!result.Succeeded)
{
if (result.Errors != null)
{
AddErrors(result);
}
if (ModelState.IsValid)
{
// No ModelState errors are available to send, so just return an empty BadRequest.
return BadRequest();
}
return BadRequest(ModelState);
}
return null;
}
private void AddErrors(IdentityResult result)
{
foreach (string error in result.Errors)
{
ModelState.AddModelError("", error);
}
}
Im new to MVC and WebApi and have mainly been following tutorials, this is assume is basic stuff but i cant find a solution anywhere. I have separated the WebApi from the project intentionally so I can learn how these processes work better.
I'd like the solution not to be in javascript.
I'm assuming all subsequent requests will need to have a bearer token attached to the httpclient but i think this will be in another question.
Thanks in advance for any help on this
All works fine but I cant figure out how to return errors back to the MVC project such as: "password must contain Upper case and lower case" etc.
You can send custom message in BadRequest() method overload. When you perform custom validation on Model just return a custom message.
if (ValidatePasswordPolicy(model.Password))
{
return BadRequest("password must contain Upper case and lower case.");
}
Update:
There's a difference in ModelState validation of Asp.net mvc app and WebAPI. When you're using ModelState to put errors in WebAPI then you have to properly handle the response. Proper handling means the response is succeeded or not. If not then are there ModelState errors or other error. Below snippet will show you how to deserialize the error to anonymous object and check if there are model state errors or other error. In case of ModelState errors you can simply add them to your Asp.net mvc Model and return the view with model to update UI with errors.
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsAsync<T>();
}
else
{
var httpErrorObject = await response.Content.ReadAsStringAsync();
var anonymousErrorObject =
new { message = "", ModelState = new Dictionary<string, string[]>() };
// Deserialize:
var deserializedErrorObject =
JsonConvert.DeserializeAnonymousType(httpErrorObject, anonymousErrorObject);
// Check if there are actually model errors
if (deserializedErrorObject.ModelState != null)
{
var errors =
deserializedErrorObject.ModelState
.Select(kvp => string.Join(". ", kvp.Value));
for (int i = 0; i < errors.Count(); i++)
{
// Add errors to ModelState in Asp.net mvc app
}
}
// Othertimes, there may not be Model Errors:
else
{
var error =
JsonConvert.DeserializeObject<Dictionary<string, string>>(httpErrorObject);
foreach (var kvp in error)
{
// Now this is not model error so you can throw exception
// or any custom action what ever you like
}
}
}
There's a detailed article about handling errors from WebAPI in Asp.net mvc App here.

Is it possible to use Asp.net Identity with autoincremental key fields?

I am use my own EF database using long as the Id field. I have created all the entities and the relevant custom stores.
However when registering a new user, it is trying to call SetPasswordHashAsync before it has called CreateAsync, which means that it is trying to write to a database record that doesn't exist yet and of course it is throwing an error.
It looks like the system is expecting to know the Id field before it has been written to the DB but until then it cannot know the Id as it is set to be an auto-incremental field.
What am I missing? Is this even possible?
Here is the code from the AccountController (taken from the MVC project template)
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new User { UserName = model.Email, Email = model.Email };
var result = await userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
// We do not want the user to be logged in as soon as they register. We want them have to go through the login process.
//await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
// Send an email with this link
string code = await userManager.GenerateEmailConfirmationTokenAsync(user.Id);
var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
await userManager.SendEmailAsync(user.Id, "Confirm your account", "Please confirm your account by clicking here");
return View("DisplayEmail");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
and here is the code from UserManager (from Identity.core) that is called
public virtual async Task<IdentityResult> CreateAsync(TUser user, string password)
{
ThrowIfDisposed();
var passwordStore = GetPasswordStore();
if (user == null)
{
throw new ArgumentNullException("user");
}
if (password == null)
{
throw new ArgumentNullException("password");
}
var result = await UpdatePasswordInternal(passwordStore, user, password).WithCurrentCulture();
if (!result.Succeeded)
{
return result;
}
return await CreateAsync(user).WithCurrentCulture();
}
So you can see that updating the password before calling createUser is the intended process, but how is that meant to work? That just seems wrong to me.

Categories