Asp.Net Core TempData attribute - value is always null - asp.net

I'm using the [TempData] attribute on a property of my Controller class, e.g:
public class FooController : Controller
{
[TempData]
public string ReturnUrl { get; set; }
....
}
And setting this value in an Index action handling a GET request:
public IActionResult Index(string returnUrl = null)
{
this.ReturnUrl = returnUrl;
// Do stuff...
return View();
}
I have a second action handling a POST request which is raised from the first (Index) action, it's here that I need to read the TempData value back:
[HttpPost]
public IActionResult HandlePost(int id)
{
// Do post things...
// Read value from [TempData] backed property
string returnUrl = this.ReturnUrl;
return this.Redirect(returnUrl);
}
However I'm finding that the value of the ReturnUrl property is always null.
But if I use TempData directly like this:
// Set return url
TempData["returnUrl"] = returnUrl;
And
// Get return url
string returnUrl = TempData["returnUrl"] as string;
It works fine - am I using the attribute incorrectly? Is it possible to use [TempData] in this way?

I can get returnUrl with this.ReturnUrl,here is a demo:
Controller:
public class Test1Controller : Controller
{
[TempData]
public string ReturnUrl { get; set; }
public IActionResult Index(string returnUrl)
{
this.ReturnUrl = returnUrl;
return View();
}
[HttpPost]
public IActionResult HandlePost()
{
string returnUrl = this.ReturnUrl;
return Ok();
}
}
Index.cshtml:
<form method="post" asp-action="HandlePost">
<input type="submit" value="submit"/>
</form>
url:
https://localhost:44355/Test1?returnUrl=ssss
result:

Related

Populate a select list ASP.NET Core MVC

I'm busy with an ASP.NET Core MVC application, and I'm trying to populate a drop down list. I've created a view model and I have added a method to my StoresController that returns a list of stores that I want to display in a dropdown. I've been working off some online tutorials as I'm very new to asp.
View model:
public class StoreListViewModel
{
public List<StoreList> StoreList { get; set; } = new List<StoreList>();
}
public class StoreList
{
public string StoreId { get; set; } = null!;
public string StoreName { get; set; } = null!;
}
StoresController:
public IActionResult LoadStoreList()
{
if (ModelState.IsValid)
{
var storeList = new StoreListViewModel().StoreList.Select
(x => new SelectListItem { Value = x.StoreId, Text = x.StoreName }).ToList();
ViewBag.Stores = storeList;
}
return NotFound();
}
I'm trying to use ViewBag to call my LoadStoreList() method.
<select name="storeList" class="form-control" asp-items="#(new SelectList(ViewBag.Stores, "Value", "Text"))"></select>
When I load my page I get the following error
Value cannot be null. (Parameter 'items')
The page I need the dropdown list on is my CreateUser.cshtml which is bound to my UserModel and has a UsersController. The method I have created for listing the stores is in my StoresController which is bound to my StoresModel. So I'm not sure if that's causing the issue.
I've been battling with this for days, if someone could help me get this working or show me a better method, that would be great.
*Edit
The UserIndex() method is the first method that fires when my users page opens, do I call the LoadStoreList() method from there ?
UserController
public async Task<IActionResult> UsersIndex()
{
return _context.UsersView != null ?
View(await _context.UsersView.ToListAsync()) :
Problem("Entity set 'ApplicationDbContext.Users' is null.");
}
I'm trying to use ViewBag to call my LoadStoreList() method.
ViewBag cannot be used to call any method. You just need set value for ViewBag in the method which renders your show dropdownlist's page.
From your description, you said the page you need the dropdown list on is CreateUser.cshtml. Assume that you render the CreateUser.cshtml page by using CreateUser action.
CreateUser.cshtml:
<select name="storeList" class="form-control" asp-items="#(new SelectList(ViewBag.Stores, "Value", "Text"))"></select>
Controller:
public class YourController : Controller
{
private readonly YourDbcontext _context;
public YourController(YourDbcontext context)
{
_context = context;
}
[HttpGet]
public IActionResult CreateUser()
{
var storeList = _context.StoreLists.Select
(x => new SelectListItem { Value = x.StoreId , Text = x.StoreName }).ToList();
ViewBag.Stores = storeList;
return View();
}
}
YourDbcontext should be something like:
public class YourDbcontext: DbContext
{
public YourDbcontext(DbContextOptions<MvcProjContext> options)
: base(options)
{
}
public DbSet<StoreList> StoreLists{ get; set; }
}
Dont use viewbag for storing list data. Make your view page model including List, for example:
public class UserCreationViewModel{
public int Id{ get; set; }
public string Name { get; set; }
// Any other properties....
public List<StoreList> StoreList { get; set; }
}
in your controller YourController:
[HttpGet]
public IActionResult CreateUser()
{
var storeList = new StoreListViewModel().StoreList.Select
(x => new SelectListItem { Value = x.StoreId, Text = x.StoreName }).ToList();
UserCreationViewModel model=new UserCreationViewModel{
StoreList = storeList
};
return View("createUserViewName", model);
}
in createUserViewName:
#Html.DropDownList("StoreId", new SelectList(Model.StoreList, "StoreId", "StoreName"), "Select", new { #class = "form-control" })
or
<select class="form-control" asp-for="#Model.StoreId" asp-items="#(new SelectList(Model.StoreList, "StoreId", "StoreName"))">
<option value="-1">Select</option>
</select>

ASP Net Core Auth - RedirectToAction back to Target Action

In an ASP.NET Core Rest API project, I have set up custom authentication, and I can annotate Controller Actions with the [Authorize] attribute, which redirects unauthorized requests back to my AuthController:
[Route("api/[controller]")]
[ApiController]
public class ResponseController : ControllerBase
{
[Authorize]
[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
return JsonConvert.SerializeObject(Repository.GetResponse(id), Formatting.Indented);
}
}
[Route("api/[controller]")]
[ApiController]
public class MetaController : ControllerBase
{
[Authorize]
[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
return JsonConvert.SerializeObject(Repository.GetMeta(id), Formatting.Indented);
}
}
[Route("api/[controller]")]
[ApiController]
public class AuthController : Controller
{
UserManager _userManager;
public AuthController(UserManager userManager)
{
_userManager = userManager;
}
[HttpGet]
[HttpPost]
public ActionResult<string> LogIn()
{
try
{
//authenticate
var username = Request.Headers["username"];
var password = Request.Headers["pass"];
if (String.IsNullOrEmpty(username) || String.IsNullOrEmpty(password))
throw new Exception("Authentication Exception: Missing Username or Password");
Task.Run(async () => {
await _userManager.SignInAsync(this.HttpContext, username, password);
}).GetAwaiter().GetResult();
return RedirectToAction("Search", "Home", null);
//^^^ How to send back to intended action?^^^
}
catch (Exception ex)
{
return AuthError();
}
}
}
This works, except how do I use the RedirectToAction method to return back to the intended controller (MetaController or ResponseController, in this case)? (the one with the method marked [Authorize], which put us into this authentication controller to begin with)
You need to use returnUrl parameter, like so:
[HttpGet]
[HttpPost]
public async Task<IActionResult> LogIn(string returnUrl = null)
{
try
{
//authenticate
var username = Request.Headers["username"];
var password = Request.Headers["pass"];
if (String.IsNullOrEmpty(username) || String.IsNullOrEmpty(password))
throw new Exception("Authentication Exception: Missing Username or Password");
await _userManager.SignInAsync(this.HttpContext, username, password);
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
return RedirectToAction("Search", "Home", null);
}
catch (Exception ex)
{
return BadRequest(new {error = "Authentication Failed"});
}
}
I also fix async/await for controller's action. You need use async Task<ActionResult> insted of your ActionResult<string>
Instead of redirection, create your own attribute inheriting from the AuthorizeAttribute and override the OnAuthorization method. That way you don't have to worry about redirection.

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 to add the UserId to posted data by Logged User in ASP.NET MVC 4

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.

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.

Resources