How to add the UserId to posted data by Logged User in ASP.NET MVC 4 - asp.net

So what I'm doing might seem simple, but I don't know exactly how to do it.
I have already registered and logged in with an account (I'm using the default membership system used in ASP.NET MVC 4) and so I want to do add my UserId to some data I'm inserting to the database.
This is the model of the data I'm inserting:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace Reroute.Models
{
public class Request
{
public int RequestId { get; set; }
// I want to add UserId based on my current session
public int UserId { get; set; }
public string OrderNumber { get; set; }
public string TrackingNumber { get; set; }
public string CurrentAddress { get; set; }
public string NewAddress { get; set; }
public string Comment { get; set; }
}
}
And the ActionResult (here's where I supposed I have to make the changes):
[HttpPost]
public ActionResult Create(Request collection)
{
try
{
_db.Requests.Add(collection);
_db.SaveChanges();
//return RedirectToAction("Index");
return Content("Done! Added to DB");
}
catch
{
return View();
}
}
Thanks

use this it gets u the userid ...
Membership.GetUser().ProviderUserKey

You can save the UserId of the authenticated user in Session after logging in:
Session["UserId"] = userId;
or since you are using FormsAuthentication you can either use the UserData property as shown here or do a nice-that-will-do-trick:
public SignInUser(string name, string id) {
// store the userid
FormsAuthentication.SetAuthCookie(name + '|' + id, false);
}
then retrieve the Name and UserId like this:
public int CurrentUserId
{
get
{
var context = HttpContext.Current;
if (context == null) return 0;
return context.Request.IsAuthenticated
? Convert.ToInt32(context.User.Identity.Name.Split('|')[1])
: 0;
}
}
public string CurrentUserName
{
get
{
var context = HttpContext.Current;
if (context == null) return string.Empty;
return context.Request.IsAuthenticated
? context.User.Identity.Name.Split('|')[0]
: string.Empty;
}
}
You can have those method and properties in a class so you have them in one place, I actually do it that way. Now, you can call it in your controller like so:
[HttpPost]
public ActionResult Create(Request collection)
{
try
{
collection.UserId = _authProvider.CurrentUserId;
// if you want to use session, I prefer the FormsAuthentication approach
// you need to do additional check that the Session has not expired (not null)
collection.UserId = Session["UserId"];
_db.Requests.Add(collection);
_db.SaveChanges();
//return RedirectToAction("Index");
return Content("Done! Added to DB");
}
catch
{
return View();
}
}
_authProvider is an instance of the class that has the code I gave above.

This should work.
var loggedInUserName=Thread.CurrentPrincipal.Identity.Name;
var user=Membership.GetUser(loggedInUserName);
var key = user.ProviderUserKey;
T

Assuming your Create also has a GET which is loaded up and used as the model for Create.cshtml, you would just need to set it explicitly in that ActionResult
public ActionResult Create()
{
Result model = new Result();
model.UserId = myUserId;
}
Then in your Create.cshtml you could have a hidden field for it:
#Html.HiddenFor(m => m.UserId)
I would still check in the POST to make sure the user doing the saving is allowed to be saving and hasn't spoofed your hidden field value to somebody completely different.

Related

What would be the best/simplest solution to retrieve values from my database for comparison? - ASP.NET Core

I'm currently stuck on accessing all of the 'UserName' values from my database.
I am doing this so I can compare the user input for a username to check if it has been used before (I don't want two instances of the same username). This is on a .cshtml.cs page.
I am already able to access the database through my program, as create commands have been tested and do work.
My program is on ASP.NET 6.0 Core Web App.
I am a student with basic knowledge on ASP.NET Core, and on how to solve this issue, therefore as much simplified explanation would be very appreciated.
Here is my code:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using AQA_A_Level_CS_NEA__Suvat_Solver_.Models;
using AQA_A_Level_CS_NEA__Suvat_Solver_.Data;
namespace AQA_A_Level_CS_NEA__Suvat_Solver_.Pages.UserLogin
{
[BindProperties(SupportsGet = true)]
public class RegisterPageModel : PageModel
{
public new TempUserLoginModel TempUser { get; set; }
public bool HasPassword { get; set; } = true;
public bool HasUsername { get; set; } = true;
public bool UniUsername { get; set; } = true;
public bool RegisterApproved { get; set; } = false;
public bool AQAPhys { get; set; } = false;
public bool AQAMaths { get; set; } = false;
public bool SubjectChosen { get; set; } = true;
private readonly ApplicationDbContext _context;
public RegisterPageModel(ApplicationDbContext context)
{
_context = context;
}
public List<User> UserList = new List<User>();
public void OnGet()
{
}
public IActionResult OnPost()
{
User User = new User();
HasPassword = true;
HasUsername = true;
UniUsername = true;
SubjectChosen = true;
UserList = _context.User.ToList();
if (!AQAMaths && !AQAPhys)
{
SubjectChosen = false;
}
if (string.IsNullOrWhiteSpace(TempUser.Password) || TempUser.Password.Length < 4)
{
HasPassword = false;
}
if (string.IsNullOrWhiteSpace(TempUser.Username) || TempUser.Username.Length < 4)
{
HasUsername = false;
}
if (TempUser.Username == //database UserName value here )
{
//Here would be where the Username is compared
//UniUsername = false;
}
if (!HasPassword || !HasUsername || !UniUsername || !SubjectChosen)
{
return RedirectToPage("/UserLogin/RegisterPage", new { HasPassword, HasUsername, UniUsername, SubjectChosen });
}
else
{
RegisterApproved = true;
User.UserName = TempUser.Username;
User.UserPass = TempUser.Password;
User.UserCorrectAnsw = 0;
User.UserTotalAnsw = 0;
_context.User.Add(User);
_context.SaveChanges();
return RedirectToPage("/UserLogin/LoginPage", new { RegisterApproved });
}
}
}
}
Many Thanks.
Probably the strongest method is to enforce the user name column to be unique at the database level using a Unique Constraint. That way if you try to add a user with a duplicate user name, the database will simply return an error.
This article shows how to create a Unique Constraint with Entity Framework
You can be sure that the database will not allow a user with a duplicate user name with this method. However, trying to add a duplicate user will create an error which you will have to either handle or prevent from occurring in the first place (which is what you are doing now)
So for the code you are using now, since you already have the users pulled from the database here:
UserList = _context.User.ToList();
We can use LINQ to check if any of the users Usernames in UserList matches the TempUser like this:
if (UserList.Any(x => x.Username == TempUser.Username))
{
//Here would be where the Username is compared
UniUsername = false;
}
Since you didn't share your User model, this assumes your User class has a property named Username.
Happy Coding

Combine [FromBody] with [FromHeader] in WebAPI in .net Core 3.0

we are writing some API which required sessionId in header and some other data in body.
Is it possible to have only one class automatically parsed partially from header and from body?
Something like:
[HttpGet("messages")]
[Produces("application/json")]
[Consumes("application/json")]
[Authorize(Policy = nameof(SessionHeaderKeyHandler))]
public async Task<ActionResult<MessageData>> GetPendingClockInMessages(PendingMessagesData pendingMessagesRequest)
{
some body...
}
with request class like:
public class PendingMessagesData
{
[FromHeader]
public string SessionId { get; set; }
[FromBody]
public string OrderBy { get; set; }
}
I know, it is possible to do this, but it means, that I have to pass SessionId into the other methods as a parameter, instead of pass only one object. And we would have to do that in every API call.
public async Task<ActionResult<MessageData>> GetPendingClockInMessages(
[FromHeader] string sessionId,
[FromBody] PendingMessagesData pendingMessagesRequest)
{
some body...
}
Thank you,
Jakub
we are writing some API which required sessionId in header and some other data in body. Is it possible to have only one class automatically parsed partially from header and from body
Your GetPendingClockInMessages is annotated with a [HttpGet("messages")]. However, a HTTP GET method has no body at all. Also, it can't consume application/json. Please change it to HttpPost("messages")
Typically, SessionId is not passed in header of Session: {SessionId} like other HTTP headers. Session are encrypted via IDataProtector. In other words, you can't get it by Request.Headers["SessionId"].
Apart from the above two facts, you can create a custom model binder to do that.
Since the Session doesn't come from header directly, let's create a custom [FromSession] attribute to replace your [FromHeader]
public class FromSessionAttribute : Attribute, IBindingSourceMetadata
{
public static readonly BindingSource Instance = new BindingSource("FromSession", "FromSession Binding Source", true, true);
public BindingSource BindingSource { get { return FromSessionAttribute.Instance; } }
}
And since you're consuming application/json, let's create a binder as below:
public class MyModelBinder : IModelBinder
{
private readonly JsonOptions jsonOptions;
public MyModelBinder(IOptions<JsonOptions> jsonOptions)
{
this.jsonOptions = jsonOptions.Value;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var type = bindingContext.ModelType;
var pis = type.GetProperties();
var result= Activator.CreateInstance(type);
var body= bindingContext.ActionContext.HttpContext.Request.Body;
var stream = new System.IO.StreamReader(body);
var json = await stream.ReadToEndAsync();
try{
result = JsonSerializer.Deserialize(json, type, this.jsonOptions.JsonSerializerOptions);
} catch(Exception){
// in case we want to pass string directly. if you don't need this feature, remove this branch
if(pis.Count()==2){
var prop = pis
.Where(pi => pi.PropertyType == typeof(string) )
.Where(pi => !pi.GetCustomAttributesData().Any(ca => ca.AttributeType == typeof(FromSessionAttribute)))
.FirstOrDefault();
if(prop != null){
prop.SetValue( result ,json.Trim('"'));
}
} else{
bindingContext.ModelState.AddModelError("", $"cannot deserialize from body");
return;
}
}
var sessionId = bindingContext.HttpContext.Session.Id;
if (string.IsNullOrEmpty(sessionId)) {
bindingContext.ModelState.AddModelError("sessionId", $"cannot get SessionId From Session");
return;
} else {
var props = pis.Where(pi => {
var attributes = pi.GetCustomAttributesData();
return attributes.Any( ca => ca.AttributeType == typeof(FromSessionAttribute));
});
foreach(var prop in props) {
prop.SetValue(result, sessionId);
}
bindingContext.Result = ModelBindingResult.Success(result);
}
}
}
How to use
Decorate the property with a FromSession to indicate that we want to get the property via HttpContext.Sessino.Id:
public class PendingMessagesData
{
[FromBody]
public string OrderBy { get; set; } // or a complex model: `public MySub Sub{ get; set; }`
[FromSession]
public string SessionId { get; set; }
}
Finally, add a modelbinder on the action method parameter:
[HttpPost("messages")]
[Produces("application/json")]
[Consumes("application/json")]
public async Task<ActionResult> GetPendingClockInMessages([ModelBinder(typeof(MyModelBinder))]PendingMessagesData pendingMessagesRequest)
{
return Json(pendingMessagesRequest);
}
Personally, I would prefer another way, i.e, creating a FromSessionBinderProvider so that I can implement this without too much effort. :
public class FromSessionDataModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var sessionId = bindingContext.HttpContext.Session.Id;
if (string.IsNullOrEmpty(sessionId)) {
bindingContext.ModelState.AddModelError(sessionId, $"cannot get SessionId From Session");
} else {
bindingContext.Result = ModelBindingResult.Success(sessionId);
}
return Task.CompletedTask;
}
}
public class FromSessionBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null) { throw new ArgumentNullException(nameof(context)); }
var hasFromSessionAttribute = context.BindingInfo?.BindingSource == FromSessionAttribute.Instance;
return hasFromSessionAttribute ?
new BinderTypeModelBinder(typeof(FromSessionDataModelBinder)) :
null;
}
}
(if you're able to remove the [ApiController] attribute, this way is more easier).

Returning a list opens a blank page with requested values

In my controller I have a function to get the roles that belong to a user.
This function is using .GetRolesAsync() and is returning an IList.
In the browser the user can submit a name, and see the roles for a user.
This however returns my list in a blank page. example
Controller :
[HttpPost]
public async Task<IList<string>> GetRoles(UserRoleViewModel model)
{
ApplicationUser user = await _userManager.FindByEmailAsync(model.Email);
if(user != null)
{
model.GetRoles = await _userManager.GetRolesAsync(user);
}
return model.GetRoles;
}
How can I return this list to my ViewModel without opening a blank page so I can call this from my view ?
ViewModel :
public class UserRoleViewModel
{
public List<SelectListItem> Roles { get; set; }
public IList<string> GetRoles { get; set; }
public string Role { get; set;}
public string Email { get; set; }
public string CurrentPassword { get; set; }
public string NewPassword { get; set; }
}
As per my knowledge, you are getting the blank page because , when the below method get executes,
Existing code :
[HttpPost]
public async Task<IList<string>> GetRoles(UserRoleViewModel model)
{
ApplicationUser user = await _userManager.FindByEmailAsync(model.Email);
if(user != null)
{
model.GetRoles = await _userManager.GetRolesAsync(user);
}
return model.GetRoles;
}
It return just result of list and doesn't return any actionresult to retain in the same view
Solution :
Make the returntype of the method like below
Replaced code :
[HttpPost]
public async Task<ActionResult> GetRoles(UserRoleViewModel model)
{
ApplicationUser user = await _userManager.FindByEmailAsync(model.Email);
if(user != null)
{
model.GetRoles = await _userManager.GetRolesAsync(user);
}
return view("viewname")
//Note: here viewname can be the same view ,where this post method was
// called if you dont want to create new view
}
And specify the view name of which you called this post method, if you dont want to create a new view.
So that it wont give a blank page for the user , when it is called.
Hope the above information was useful , kindly let me know your thoughts or feedbacks
Thanks
Karthik

How do I pass data between controllers and overloaded actions?

I have one controller that takes a username and pass and checks against a database. IF the user is authenticated, I want to call an overloaded action on another controller.
My end goal is to authenticate a user against an old table from a MySQL db (I have this part working). Once the user is authenticated, I would like to be able to "automagically" forward the person to the built in MVC registration page but I would like to populate some fields in the view using data obtained from the first controller (the old databse info).
When I try something like what I have below I get an error about the Register() methods being ambiguous. I've also tried using the [ActionName("Register2")] attribute but then the error returned says it cant find a method named Register2.
public class MigrateAccountController : Controller
{
OldUserRepository oldDb = new OldUserRepository();
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(User u)
{
if (oldDb.isValid(u.username, u.password))
return RedirectToAction("Register", "Account", u);
return View(u);
}
}
public class AccountController : Controller
{
public IFormsAuthenticationService FormsService { get; set; }
public IMembershipService MembershipService { get; set; }
protected override void Initialize(RequestContext requestContext)
{
if (FormsService == null) { FormsService = new FormsAuthenticationService(); }
if (MembershipService == null) { MembershipService = new AccountMembershipService(); }
base.Initialize(requestContext);
}
public ActionResult Register(User u)
{
return View(u);
}
public ActionResult Register()
{
ViewBag.PasswordLength = MembershipService.MinPasswordLength;
return View();
}
}
First thing you cannot have the same action name on the same controller that is accessible on the same verb. You need to either change the action name or use a different HTTP verb:
public class AccountController : Controller
{
public IFormsAuthenticationService FormsService { get; set; }
public IMembershipService MembershipService { get; set; }
protected override void Initialize(RequestContext requestContext)
{
if (FormsService == null) { FormsService = new FormsAuthenticationService(); }
if (MembershipService == null) { MembershipService = new AccountMembershipService(); }
base.Initialize(requestContext);
}
[HttpPost]
public ActionResult Register(User u)
{
return View(u);
}
public ActionResult Register()
{
ViewBag.PasswordLength = MembershipService.MinPasswordLength;
return View();
}
}
and in order to pass data between actions, well, if you are using GET, you could pass them as query string parameters when redirecting.
Or IMHO a better way would be not to redirect in this case but simply return the corresponding view by passing it the proper view model:
[HttpPost]
public ActionResult Index(User u)
{
if (oldDb.isValid(u.username, u.password))
{
return View("~/Account/Register.aspx", u);
}
return View(u);
}
You can use the TempData values in this case.

Custom form authentication / Authorization scheme in ASP.net MVC

I am trying to create a custom authentication scheme in ASP.NET MVC using form authentication. The idea that I might have different areas on the site that will be managed - approver are and general user area, and these will use different login pages, and so forth. So this is what I want to happen.
User access restricted page (right now I have it protected with a customer AuthorizeAttribute)
User is redirected to a specific login page (not the one from Web.config).
User credentials are verified (via custom databse scheme) and user logs in.
Would really appreciate any help with this!!!
This is what I what I have so far, and it doesn't work:
public class AdministratorAccountController : Controller
{
public ActionResult Login()
{
return View("Login");
}
[HttpPost]
public ActionResult Login(AdministratorAccountModels.LoginModel model, string returnUrl)
{
if (ModelState.IsValid)
if (model.UserName == "admin" && model.Password == "pass") // This will be pulled from DB etc
{
var ticket = new FormsAuthenticationTicket(1, // version
model.UserName, // user name
DateTime.Now, // create time
DateTime.Now.AddSeconds(30), // expire time
false, // persistent
""); // user data
var strEncryptedTicket = FormsAuthentication.Encrypt(ticket);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, strEncryptedTicket);
Response.Cookies.Add(cookie);
if (!String.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
// If we got this far, something failed, redisplay form
return View(model);
}
[AdministratorAuthorize]
public ActionResult MainMenu()
{
return View();
}
public class AdministratorAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var authenCookie = httpContext.Request.Cookies.Get(FormsAuthentication.FormsCookieName);
if (authenCookie == null) return false;
var ticket = FormsAuthentication.Decrypt(authenCookie.Value);
var id = new FormsIdentity(ticket);
var astrRoles = ticket.UserData.Split(new[] { ',' });
var principal = new GenericPrincipal(id, astrRoles);
httpContext.User = principal;
return true;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
var model = new AdministratorAccountModels.LoginModel();
var viewData = new ViewDataDictionary(model);
filterContext.Result = new ViewResult { ViewName = "Login", ViewData = viewData };
}
}
}
I used a combination of code suggested by minus4 and my own code above to create this simplified scenario that might help someone else. I added some comments about things that confused me at first.
public class AdministratorAccountController : Controller
{
public ActionResult Login()
{
return View("Login");
}
[HttpPost]
public ActionResult Login(AdministratorAccountModels.LoginModel model, string returnUrl)
{
if (ModelState.IsValid)
// Here you would call a service to process your authentication
if (model.UserName == "admin" && model.Password == "pass")
{
// * !!! *
// Creating a FromsAuthenticationTicket is what
// will set RequestContext.HttpContext.Request.IsAuthenticated to True
// in the AdminAuthorize attribute code below
// * !!! *
var ticket = new FormsAuthenticationTicket(1, // version
model.UserName, // user name
DateTime.Now, // create time
DateTime.Now.AddSeconds(30), // expire time
false, // persistent
""); // user data, such as roles
var strEncryptedTicket = FormsAuthentication.Encrypt(ticket);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, strEncryptedTicket);
Response.Cookies.Add(cookie);
// Redirect back to the page you were trying to access
if (!String.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
// If we got this far, something failed, redisplay form
return View(model);
}
[AdminAuthorize]
public ActionResult MainMenu()
{
return View();
}
public class AdminAuthorize : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!filterContext.RequestContext.HttpContext.Request.IsAuthenticated)
{
// Redirect to the needed login page
// This can be pulled from config file or anything else
filterContext.HttpContext.Response.Redirect("/AdministratorAccount/Login?ReturnUrl="
+ HttpUtility.UrlEncode(filterContext.HttpContext.Request.RawUrl));
}
base.OnActionExecuting(filterContext);
}
}
}
okay here you go The Code
in there you have ActionFilters folder ( AuthAccess.cs)
Plugins Folder (security.cs (encrypt/decrypt cookie), SessionHandler.cs (all matters of login))
Controllers folder (BaseController.cs, and exampleController (show you how to use)
and the loginTable SQL file.
i use mysql so you may need to amend, also i use subsonic so my model would come from there
and would be in the empty models folder.
really simple to use will leave it up for a while for you, enjoy
nope cookie model is here sorry:
using System;
namespace TestApp.Models
{
public class CookieModel
{
public string CurrentGuid { get; set; }
public DateTime LoginTime { get; set; }
public Int32 UserLevel { get; set; }
public Int32 LoginID { get; set; }
public bool isValidLogin { get; set; }
public string realUserName { get; set; }
public string emailAddress { get; set; }
}
}
Isn't this what roles are for?
Have a look at asp.net mvc authorization using roles or have a look at roles in general
i tackled this one before i have a class i use for login
routines are login, read cookie, check cookie and they have a model that contains
name, email, id, userlevel
then you just have your own custom actionFilter
eg [CustomAuth(MinAllowedLevel=10)]
i use a baseclass for all my controllers so i can have an easier link to
all my session content and can then get info like so
var model = pictures.all().where(x => x.userid == users.ReadCookie.userID)
i will bob up the code tommorow if you want for you when im back on UK daytime
say 10 hrs i will let you have the class for all the session stuff and the
custom action filter that you can use, then all you need is a logins table with a userlevel field, best with levels of 10,20,30,40 incase you need a level between 1 and 2

Resources