I am trying to retrieve the current logged on user details in a Web API controller to create a user profile page.
I have got the method created but the user keeps returning null in the code was shown below. This works fine in my MVC controller but in Web API controller it throws an error.
[HttpGet]
public IActionResult userProfile()
{
if (ModelState.IsValid)
{
var user = _userManager.Users.First(x => x.Email == User.Identity.Name);
return Ok(new UserViewModel
{
Id = user.Id,
UserName = user.Email,
FirstName = user.FirstName,
LastName = user.LastName
});
}
else
{
return BadRequest(ModelState);
}
}
UPDATE
[HttpGet]
[Authorize(Roles = "Client, Administrator")]
public IActionResult userProfile()
{
string baseUrl = "https://localhost:5001";
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(baseUrl);
var contentType = new MediaTypeWithQualityHeaderValue("application/json");
client.DefaultRequestHeaders.Accept.Add(contentType);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", HttpContext.Session.GetString("token"));
UserViewModel userModel = new UserViewModel();
HttpResponseMessage response = client.GetAsync("https://localhost:5001/api/UserAPI").Result;
string stringData = response.Content.ReadAsStringAsync().Result;
userModel = JsonConvert.DeserializeObject<UserViewModel>(stringData);
return View(userModel);
}
Error Message:
System.InvalidOperationException: 'Sequence contains no elements
An API is stateless meaning that there is no concept of logged in users or sessions. This is because each request is unique, separate and holds all the information required to provide a response.
An API has no way of knowing who is sending a request, there can be 10k people all sending requests at the same time, so who exactly is "logged in"?
So, if you want to load a user profile then send the userID as a parameter, something like:
[HttpGet]
public IActionResult userProfile(string userEmail)
{
if ( !string.IsNullOrEmpty (userEmail) ) ..... etc etc
{
var user = _userManager.Users.First(x => x.Email == userEmail);
return Ok(new UserViewModel
{
Id = user.Id,
UserName = user.Email,
FirstName = user.FirstName,
LastName = user.LastName
});
}
}
As a side note, if you don't have any input parameters or the input is a primitive type such as string or int, then ModelState.IsValid won't do anything. Only use ModelState.IsValid if you have a model class populated with the right rules.
in my case I could actually replace the string with a class
public class UserProfileRetrieveModel
{
[Required]
public string UserEmail { get;set; }
}
then I can do :
[HttpGet]
public IActionResult userProfile(UserProfileRetrieveModel model)
{
if (ModelState.IsValid)
{
var user = _userManager.Users.First(x => x.Email == model.UserEmail);
//etc
}
else
{
return BadRequest(ModelState);
}
}
--- after question updated
so it looks like you have a client application and from that you call the API.
everything I said above still applies, simply populate the data you have before calling the API.
Example:
public IActionResult userProfile()
{
//removed code we don't care about
string userEmail = "";// get your user email here in the MVC controller
//populate it in the api url.
//you might need to URL encode it since it will contain dots and #
string apiUrl = "https://localhost:5001/api/UserAPI/{userEmail}";
HttpResponseMessage response = client.GetAsync(apiUrl).Result;
}
you don't need to work out anything in the API, just pass everything you need to the API, from the MVC controller.
Now, all this aside, you have massive async issues. that part needs work although it's not related to the question.
Im presuming you're on WebAPI 2.
Try the following:
var user = _userManager.GetUser(HttpContext.User);
or
var user = _userManager.FindById(User.Identity.GetUserId());
Instead of .First() change it to .FirstOrDefault()
From the error message It seems you are trying to retrieve an element from an empty sequence. So check whether you have the the data or not.And also try to use .FirstOrDefault().
Related
I have a general question and I can't seem to find any answer in other topics.
So I'll show my code here:
This is my register.component.ts:
email = this.registerForm.controls['email'].value;
password = this.registerForm.controls['password'].value;
// call RegisterController
this.http.post('api/register', params).subscribe(params => {
this.router.navigate(['']); // redirect to login
},
error => console.log(error)
);
This is my C# Controller:
[Route("api/register")]
[HttpPost]
public void Register(string email = "", string password = "")
{
email = Request.Query["email"].ToString().Trim();
password = Request.Query["password"].ToString().Trim();
...
}
My question is: how can I pass input values for email and password from angular to c#? Everytime in my controller I get "".
Just create a model in your backend to pass it to your controller, something like:
public class RegisterModel {
public string Email { get; set; }
public string Password { get; set; }
}
and in your controller you pass it
public void Register([FromBody]RegisterModel model)
{
email = model.Email;
password = model.Password;
...
}
Note that we are adding [FromBody] attribute to tell asp that the content is not coming as part of the url params
what you can do is to use HttpParams which seem more clean solution to solve you problem
let httpParams = new HttpParams()
.append("email", "test#test.com")
.append("password", "Password1");
in the end you will get this code
email = this.registerForm.controls['email'].value;
password = this.registerForm.controls['password'].value;
let httpParams = new HttpParams()
.append("email", email)
.append("password", password);
// call RegisterController
this.http.post('api/register', httpParams).subscribe(params => {
this.router.navigate(['']);
},
error => console.log(error)
);
You can create a model in asp.net webapi and send the data from angular as json (content-type:application/json) and let web api formatter deserialize it to your model object
When I have a return type of 'string' in my WebAPI controller, the SuccessStatusCode returns 'OK' in my MVC Controller, but when the return type is of a model named 'USER', I get this Internal Server Error. Here's my code:
WebAPI:
public class UserController : ApiController
{
OnlineCenterEntities db = new OnlineCenterEntities();
public USER GetUserInfo(string userName, string domain)
{
USER userInfo = (from u in db.USERs
where u.USER_NAME.ToUpper() == userName.ToUpper() && u.LDAP_NAME.ToUpper() == domain.ToUpper()
select u).FirstOrDefault();
return userInfo;
}
}
MVC Controller that calls the WebAPI:
public class HomeController : Controller
{
HttpClient client;
string url = "http://localhost:61566/api/user/";
public HomeController()
{
client = new HttpClient();
client.BaseAddress = new Uri(url);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
}
public async Task<ActionResult> Index(string userName, string domain)
{
string GetUserInfoURL = String.Format("GetUserInfo?userName={0}&domain={1}", userName, domain);
HttpResponseMessage responseMessage = await client.GetAsync(url+GetUserInfoURL);
if (responseMessage.IsSuccessStatusCode)
{
var responseData = responseMessage.Content.ReadAsStringAsync().Result;
var userInfor = JsonConvert.DeserializeObject<USER>(responseData);
}
return View();
}
USER model:
public partial class USER
{
public int USER_ID { get; set; }
public string USER_NAME { get; set; }
public string FIRST_NAME { get; set; }
public string LAST_NAME { get; set; }
public string LDAP_NAME { get; set; }
public string EMAIL { get; set; }
}
In my WebAPI, if I change the return type from USER to string (and of course, change the return variable type to some string (userInfo.FIRST_NAME)), I get the SuccessStatusCode as 'OK', but as of this code, I get Internal Server Error with StatusCode: 500 (whatever that means). I have tried inserting breakpoint at every possible points, and I know that the api is returning the result fine. I simply don't understand why the following line
HttpResponseMessage responseMessage = await client.GetAsync(url+GetUserInfoURL);
gives InternalServerError error when I have the return type of USER, and return the whole USER model instead of just one string.
Please don't worry about the userName and domain parameters that I'm passing to the controllers, they are working fine!
Typically when this happens, it means it is failing to serialize the response. Once your controller returns a USER instance, somewhere WebAPI has to serialize that into the format requested by the client.
In this case the client requested "application/json". The default JsonMediaTypeFormatter uses JSON.Net to turn your C# object into json for the client. Apparently that serialization step is failing, but the response code doesn't tell you exactly why.
The easiest way to see exactly what is happening is to use a custom MessageHandler which forces the body to buffer earlier so you can see the actual exception. Take a look at this blog post for an example to force it to show you the real failure.
I'm trying to get the querystring that is in the browser,
an example /UserHome/UserProfile?username=dangercoder. I'm working in WebApi controller
from the following url i want to get "dangercoder" so I can use the dangercoder username in my AddPost method that is in my Web Api
what i've done so far in the method (the reciever id can be hardcoded but ofcourse i want it to be dangercoder username converted to an id.
[System.Web.Http.HttpPost]
public IHttpActionResult AddPost(Post post)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var user = User.Identity.Name;
post.SenderID = UserRepository.GetUserId(user);
// QueryString that returns username in the parameter below.
post.RecieverID = UserRepository.GetUserId();
PostRepository.AddNewPost(post);
return CreatedAtRoute("DefaultApi", new { id = post.ID }, post);
}
As far as I see, the view UserProfile contains HTML form with Post fields.
You may create hidden field SenderId in the form with user's ID.
I'm still learning how to use MVC 5, and so far I manage to get custom fields for my user profiles seen here in my manage view page:
http://puu.sh/ddmVY/2533472010.png
The user registers and fills out these fields and they are stored in the same place as the username and password data are stored.
I added these fields right under the ApplicationUser in the IdentityModels.cs as seen here
public class ApplicationUser : IdentityUser
{
// Additional user fields needed for registration
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public int ZipCode { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
}
I want to be able to edit the address so that if someone moves they can update it on their own. I have the method to get the data and put them inside the textboxes in the ChangeAddress view, but when it comes time to update the data I’m not sure how to go about that. I'm not sure how to write that post method Here is what the page looks like below and I was hoping to add this in the method in the ManageController.cs. I've seen other tutorials that have it done on a separate table but not the one from the ApplicationUser.
http://puu.sh/ddn98/96cab8a252.png
My method to display the data in the ChangeAddress view page
// GET: /Manage/ChangeAddress
[HttpGet]
public async Task<ActionResult> ChangeAddress()
{
ApplicationUser user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
var model = new ChangeAddressViewModel
{
Address = user.Address,
City = user.City,
State = user.State,
ZipCode = user.ZipCode
};
return View(model);
}
You need to pass something in the model that will allow you to identify which user the data belongs to when it comes back. It would be easiest to add a UserId property to the ChangeAddressViewModel.
You pass this back with the other data in the ChangeAddressViewModel record when you make your request to the get. This information then populates your view typically in a form that can be submitted back via POST. You typically put the UserId into a HiddenField so that its present in the form but not shown.
You can then create an update method on your controller that has a [HttpPost] attribute which takes a ChangeAddressViewModel as its input parameter and then you wire up your form so that the submit posts to that update action.
Inside your new update method. locate the desired user using the userId that you have been passed back. Set the various updated values of that user from the values obtained from the ChangeAddressViewModel that was passed in by the POST.
On your DB context, for your user record call SaveCnanges() to update the record in via EF.
There is a step by step tutorial for MVC on asp.net
In the end I manage to help myself and came up with solution to my problem. Posting it here for anyone who has a similar problem to mine.
//
// POST: /Manage/ChangeAddress
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ChangeAddress(ChangeAddressViewModel model)
{
if (ModelState.IsValid)
{
// Get the current application user
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
if (user != null)
{
//grab the details
user.Address = model.Address;
user.City = model.City;
user.State = model.State;
user.ZipCode = model.ZipCode;
// Update the user
var result = await UserManager.UpdateAsync(user);
if (result.Succeeded)
{
return RedirectToAction("Index", new { Message = ManageMessageId.ChangeAddressSuccess });
}
AddErrors(result);
return View(model);
}
}
return View(model);
}
I have a problem with adding AntiForgeryToken. This is my code:
<%:Html.ActionLink("Text", "Action", new { ID = "Hello")})%>
and
RedirectToAction("Action", "Controller", new { ID = "HelloFromMe"});
Controller:
[ValidateAntiForgeryToken]
public ActionResult Action(String ID){
return View();
}
Does anybody have idea how to do it ?
It is impossible to use an AntiForgeryToken into a GET method.
GET methods should only be used for read-only operation on your server. If you want to do something else than a read-only operation, then, you should use a POST method.
Here the reason why this token is useful, how and when to use it. http://haacked.com/archive/2009/04/02/anatomy-of-csrf-attack.aspx
The idea of antiforgery token is to prevent attacker to generate POST / GET request on behalf of the user. Thats why we add something special to every POST / GET request, that is unknown to attacker.
The simplest implementation of custom antiforgery would look like this.
And it will be exactly safe as ValidateAntiForgeryToken.
public class ProfileController : AuthorizedAccessController
{
// GET
public ActionResult Details(int userId)
{
User user = this.Entities.User.First(u => u.Id == userId);
this.Session["ProfilePageAntiforgery"] = Guid.NewGuid(); // use RandomNumberGenerator to generate strong token
this.ViewBag.ProfilePageAntiforgery = this.Session["ProfilePageAntiforgery"];
return View(user);
}
public ActionResult DeleteMyProfile(int userId, string profilePageAntiforgery)
{
if ((string)this.Session["ProfilePageAntiforgery"] != profilePageAntiforgery)
{
return this.RedirectToAction("Details", new { userId });
}
User user = this.Entities.User.First(u => u.Id == userId);
this.Entities.User.Remove(user);
this.Entities.SaveChanges();
return this.RedirectToAction("ProfileDeleted");
}
}
View:
<div>
#Html.ActionLink("Delete my profile", "DeleteMyProfile", new {userId = this.Model.Id, profilePageAntiforgery = this.ViewBag.ProfilePageAntiforgery })
</div>
Making custom attributes out of this is a matter of technique.
Lets say one attribute to store token in session and viewbag and the other to validate.
If using Session is not OK (Web farm etc.) you can simply replace it by DB or other store.