Passing TempData with RedirectToAction - asp.net-core-mvc

Intro:
I am a .NET studet trying to learn ASP.NET Core MVC. So please be understanding. I have searched the web for an answer to my problem, but havent found a solution that works for me.
Problem:
I want to pass a validation message from my create post method to the index IActionmethod whenever a post has been created and them show it as an alert message for now. I have read on the web that ViewBag dosent survive a redirect, but a TempData does. This is my code so far.
Create post method:
public IActionResult CreatePost(string textContent, string headline, string type)
{
var catType = new Category() { CategoryType = type.ToUpper() };
if (db.Category.Any(s => s.CategoryType.Trim().ToLower() == type.Trim().ToLower()))
catType = db.Category.FirstOrDefault(s => s.CategoryType.Trim().ToLower() == type.Trim().ToLower());
var newPost = new Post()
{
Content = textContent,
Header = headline,
DateOfPost = DateTime.Now,
category = catType
};
db.Posts.Add(newPost);
db.SaveChanges();
TempData["validation"] = "Your post hase been publsihed";
return RedirectToAction("Index");
}
The index method:
public IActionResult Index()
{
var validation = TempData["validation"];
var posts = (from x in db.Posts
orderby x.DateOfPost descending
orderby x.PostID descending
select x);
return View(posts);
}
I have tried this guide: ClickThis and this one: ClickThis2 but I got this message:
I know this line from gudie number 2 might be important, but didnt now how to apply it. -
var product = TempData["myTempData"] as Product;
The last thing I want to do is pass it to the index view, but dont know how. I am currently passing a model from the index.
Tell me if it is anything more you would like to see. Like dependencies.
All the help I get is gold and will be much appreciate!!!

I landed on this question while googling for "asp.net core redirect to action tempdata". I found the answer and am posting it here for posterity.
Problem
My issue was that, after filling in some TempData values and calling RedirectToAction(), TempData would be empty on the page that I was redirecting to.
Solution
Per HamedH's answer here:
If you are running ASP.NET Core 2.1, open your Startup.cs file and make sure that in your Configure() method app.UseCookiePolicy(); comes after app.UseMVC();.
Example:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
app.UseCookiePolicy();
}

Did you configure Session? TempData is using session behind the scenes.
Project.json
"Microsoft.AspNetCore.Session": "1.1.0"
Here is the Startup.cs file. - ConfigureServices method
public void ConfigureServices(IServiceCollection services)
{
services.AddMemoryCache();
services.AddSession();
services.AddMvc();
}
And Configure method.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseSession();
app.UseMvc(routes => {
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
Now try with TempData, it will work.
And you can set the environment with set ASPNETCORE_ENVIRONMENT=Development environment variable.

TempData stores data server-side, under user Session. You need to enable sessions (as exception message says). Check this manual.
If you don't want to use sessions - you need some other way to store data (cookies?)

Providers
The TempData is using various providers for storing the state. By default the cookie based data provider is used.
Session is just an alternative
If your application do not use session I do not see any reason to use it only for TempData store.
Cookie Consent
ASP NET Core 2.1 have some new GDPR features based on cookies. By default, data should be stored in cookies only with the user's consent. If the user does not agree with the storing data in cookies, TempData cannot work. This behavior varies across versions of ASP NET Core.
If you do not want to hold any sensitive data in cookies, you can obviously change the settings.
app.UseCookiePolicy(new CookiePolicyOptions
{
CheckConsentNeeded = context => false
});
You can set the CookiePolicyOptions separatelly in ConfigureServices as well. It is a quite cleaner.
Story continues
We have two kind of data in the cookies. Essential data (needed for running application) and non-essential (some user data). User consent is needed for non-essential data. TempData is non-essential. You can set you TempData as essential and user consent is not needed anymore:
services.Configure<CookieTempDataProviderOptions>(options => {
options.Cookie.IsEssential = true;
});
I highly recommend to think about this before copy / paste.

I'm just posting this for anyone who comes across this problem in an ASP.NET MVC application, #Ahmar's answer made me go look at my logout method, I was using Session.Abandon() before redirecting to the login page.
I just changed it to Session.Clear() to reset the session instead of removing it completely and now the TempData is working in the method I'm redirecting to.

Related

ASP.NET Core 2.2 - Action Filter db Query Question

I have users in our app, who are mapped to companies. When a user logs in and starts to make requests I want a way to validate if that user is currently mapped to the company for access to company resources.
The idea I had was to create a whole controller just to manage all of this, but someone mentioned ActionFilters as a much better and cleaner option, I have to agree after looking at it.
The idea is to have the controller setup as:
controller - action - CompanyId - ReportId
So any request to root the system would just look up if there are any companies mapped to that logged in user.
But if the request included CompanyId then they'd go to that company's “portal” account page. It's really any request that includes CompanyId where I want the actionFilter to make a determination on if that user is allowed access.
Request comes in...
There is a CompanyId in the request!
ActionFilter:
Look up in db for all users assigned to that CompanyId. Is current user within that list? No? = kick'em out.
I tried to type in a code example, but the system told me to manually indent each line by 4 spaces, I was doing it from memory anyways so no idea how helpful it would have been anyways.
You could get your action parameters in your action filter and then get your database via HttpContext.RequestServices.GetRequiredService<ApplicationDbContext>().Refer to here.
public class TestActionFilter:Attribute,IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
//If companyId is action parameter
var companyId= context.ActionArguments["companyId"].ToString();
//If companyId1 is query string
var companyId1= context.HttpContext.Request.Query["companyId1"].ToString();
//If companyId2 is in request header
var companyId2= context.HttpContext.Request.Headers["companyId2"].ToString();
//get your dbcontext
var db = context.HttpContext.RequestServices.GetRequiredService<ApplicationDbContext>();
//EF core logic
//...
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
}
You could use it on action directly using [TestActionFilter] attribute or set as global filter
services.AddMvc(options =>
{
options.Filters.Add(new TestActionFilter()); // an instance
});

ASP.net Core RC2 Web API POST - When to use Create, CreatedAtAction, vs. CreatedAtRoute?

What are the fundamental differences of those functions? All I know is all three result in a 201, which is appropriate for a successful POST request.
I only follow examples I see online, but they don't really explain why they're doing what they're doing.
We're supposed to provide a name for our GET (1 record by id):
[HttpGet("{id}", Name="MyStuff")]
public async Task<IActionResult> GetAsync(int id)
{
return new ObjectResult(new MyStuff(id));
}
What is the purpose of naming this get function, besides that it's "probably" required for the POST function below:
[HttpPost]
public async Task<IActionResult> PostAsync([FromBody]MyStuff myStuff)
{
// actual insertion code left out
return CreatedAtRoute("MyStuff", new { id = myStuff.Id }, myStuff);
}
I notice that CreatedAtRoute also has an overload that does not take in the route name.
There is also CreatedAtAction that takes in similar parameters. Why does this variant exist?
There is also Created which expects a URL and the object we want to return. Can I just use this variant and provide a bogus URL and return the object I want and get it done and over with?
I'm not sure why there are so many variants just to be able to return a 201 to the client. In most cases, all I want to do is to return the "app-assigned" (most likely from a database) unique id or a version of my entity that has minimal information.
I think that ultimately, a 201 response "should" create a location header which has the URL of the newly-created resource, which I believe all 3 and their overloads end up doing. Why should I always return a location header? My JavaScript clients, native mobile, and desktop apps never use it. If I issue an HTTP POST, for example, to create billing statements and send them out to users, what would such a location URL be? (My apologies for not digging deeper into the history of the Internet to find an answer for this.)
Why create names for actions and routes? What's the difference between action names and route names?
I'm confused about this, so I resorted to returning the Ok(), which returns 200, which is inappropriate for POST.
There's a few different questions here which should probably be split out, but I think this covers the bulk of your issues.
Why create names for actions and routes? What's the difference between action names and route names?
First of all, actions and routes are very different.
An Action lives on a controller. A route specifies a complete end point that consists of a Controller, and Action and potentially additional other route parameters.
You can give a name to a route, which allows you to reference it in your application. for example
routes.MapRoute(
name: "MyRouteName",
url: "SomePrefix/{action}/{id}",
defaults: new { controller = "Section", action = "Index" }
);
The reason for action names are covered in this question: Purpose of ActionName
It allows you to start your action with a number or include any character that .net does not allow in an identifier. - The most common reason is it allows you have two Actions with the same signature (see the GET/POST Delete actions of any scaffolded controller)
What are the fundamental differences of those functions?
These 3 functions all perform essentially the same function - returning a 201 Created response, with a Location header pointing to the url for the newly created response, and the object itself in the body. The url should be the url at which a GET request would return the object url. This would be considered the 'Correct' behaviour in a RESTful system.
For the example Post code in your question, you would actually want to use CreatedAtAction.
[HttpPost]
public async Task<IActionResult> PostAsync([FromBody]MyStuff myStuff)
{
// actual insertion code left out
return CreatedAtAction("MyStuff", new { id = myStuff.Id }, myStuff);
}
Assuming you have the default route configured, this will add a Location header pointing to the MyStuff action on the same controller.
If you wanted the location url to point to a specific route (as we defined earlier, you could use e.g.
[HttpPost]
public async Task<IActionResult> PostAsync([FromBody]MyStuff myStuff)
{
// actual insertion code left out
return CreatedAtRoute("MyRouteName", new { id = myStuff.Id }, myStuff);
}
Can I just use this variant and provide a bogus URL and return the object I want and get it done and over with?
If you really don't want to use a CreatedResult, you can use a simple StatusCodeResult, which will return a 201, without the Location Header or body.
[HttpPost]
public async Task<IActionResult> PostAsync([FromBody]MyStuff myStuff)
{
// actual insertion code left out
return StatusCode(201);
}
I believe there is an example for you here.
Remembering that I'm using .NET 6
[HttpPost]
public IActionResult CadastrarCerveja([FromBody] Cerveja cerveja)
{
using (var ctx = new CervejaContext())
{
ctx.Cervejas.Add(cerveja);
ctx.SaveChanges();
}
return CreatedAtAction(
nameof(LerCerveja),
new { IdCerveja = cerveja.Id },
cerveja);
}
[HttpGet("{IdCerveja}")]
public IActionResult LerCerveja(int IdCerveja)
{
var ctx = new CervejaContext();
var cerv = ctx.Cervejas.FirstOrDefault(c => c.Id == IdCerveja);
if (cerv == null)
return NotFound();
else
return Ok(cerv);
}

Why am I automatically pushed to login page when a user is unauthenticated by calling the Web API?

I am using MVC 6 web api and I have a method (shown below). When the user is not authenticated (logged on) and makes the call (example url: https://localhost:44338/api/account/Test), they get automatically pushed to url:
https://localhost:44338/Account/Login?ReturnUrl=%2Fapi%2Faccount%2FTest
BUT this is a web api project and does not have any views (such as im automatically getting pushed to here).
Code:
[HttpGet("test")]
[Authorize]
public async Task<IActionResult> Test()
{
return Json("success");
}
Why am I getting automatically pushed to the login page? I've NOTHING in my startup.cs or web.config that specifies this. It seems like default behaviour? How do I disable this so I just get the 401 status?
Thanks in advance!
For convenience, here's the solution that worked for me:
services.Configure<IdentityOptions>(o =>
{
o.Cookies.ApplicationCookie.Events = new CookieAuthenticationEvents()
{
OnRedirectToLogin = ctx =>
{
if (ctx.Response.StatusCode == (int)HttpStatusCode.Unauthorized)
{
return Task.FromResult<object>(null);
}
ctx.Response.Redirect(ctx.RedirectUri);
return Task.FromResult<object>(null);
}
};
});
I was pointed to this article: mvc6 unauthorized results in redirect instead by #TrevorWard
ASP.NET automatically redirects to the login page, if you have AuthorizationAttribute defined in your App_Start/FilterConfig.cs, see if AuthorizeAttribute() is defined. If it does, remove it.
Check if you have App_Start/Startup.Auth.cs. If it does, delete it.
If Startup.cs is decorated with the attribute [assembly: OwinStartupAttribute(typeof(FleetSys.Startup))], remove that attribute.
You are probably using wrong Authorize attribute.
In MVC, you should use System.Web.Mvc.AuthorizeAttribute and that one will redirect.
In Web API, you should use System.Web.Http.AuthorizeAttribute and that one will return 401 status code.

Redirecting to a page if session object expires or user did not login to the application

This is a mvc application
I have the links below on my master page
Link1 Link2 Link3 signout signIn
I have a userprofile object that is populated
when authentication is done.
When the session object for the user expires
and I click on the links, I get the yellow page(error page).
I will prefer a situation where when you click on the
links and the session object is expired, you get
redirected to the signin page.
Soln A: I could solve this problem by writing a method
which test for null value of the userprofile object then
make a call to this method on the click on every link.
I dont want this approach because in the future if there
are new controllers, i will need to care for them.
Do you have any idea how I can solve this problem?
I would have a Base Controller than all of your other controllers inherit from. Within this I would have a
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (SessionManager.Instance() == null)
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary
{
{ "Controller", "BaseController" },
{ "Action", "LogOn" }
});
}
base.OnActionExecuting(filterContext);
}
The OnAction Executing will be hit before any method in any of your controllers - this way you can check your session is valid or your user is valid or whatever and handle the redirect to the view you want if that is not the case. If you do a Google search for Filters Actions MVC you will find out much more info.

MVC3 "Global Variable"

I am creating an MVC Intranet app with windows authentication. The data being accessed is year specific, so I need a way to set a global variable, be it a session, or whatever, that must be set before any queries are performed.
I've not been able to find a way to force a user to login since they are already authorized via their windows login credentials so using attributes to force a login seems problematic.
I am finding that users could navigate to any particular page and attempt to pull data and create a bit of havoc without the year defined.
What I would like to do is force a user to a year select page to select the year which they wish to access if the year variable is not yet set.
I've checked related questions and they do not appear to provide answer to my current conundrum.
You can write a custom ActionFilter, which you put on all your controllers (but not on the action where you have to select the year, of course). If year ain't be selected, you redirect to the controller/action where you can choose the year.
public class CheckYearAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if(<YEAR IS NOT SET>)//redirect
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary {{ "Controller", "YourController" },
{ "Action", "YourAction" } });
}
base.OnActionExecuting(filterContext);
}
}

Resources