MVC3 "Global Variable" - asp.net-mvc-3

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);
}
}

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
});

Passing TempData with RedirectToAction

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.

Updating a session and using it within my view

I have a wish list, that is throughout the shopping pages. I need to know if this makes sense/the proper way of structuring.
Store the wish list as a session, when a user adds/deletes a new item it updates the session by an ajax call that just returns true/false if successful. On the partial view of the wish list component, I check for the session and cast it to my viewModel (which the session is based on) or serialize it for my knockout.
Let me know if this makes sense, otherwise I can post some code samples
It's hard to say without having a look at your basic structure, and not knowing you exact needs.
I don't know if you know this, but you can actually access the Session directly in Views:
#{
var wishlist = (WishList)HttpContext.Current.Session["Wishlist"];
}
It's fine to use Ajax to update it server side; and then you can return a partial view from the controller, to use however you like in the Ajax success call.
I hope this makes sense.
To begin with, if the wishlist is only supposed to exist for the duration of their visit then storing it in a session would be the best thing to do. However if the wishlist is supposed to live longer than a single visit and should be available to the user upon their return then I would suggest storing it in the database against the user's credentials/account (this is presuming they have an account).
As for the session itself, whilst you can access session data from a view I would not suggest it as you start to have a dependency on the session and before long you'll have code such as this scattered throughout your views.
var wishlist = (WishList)HttpContext.Current.Session["Wishlist"];
What happens when you want to change the way the wishlist works and instead have it database driven as you'd now like to persist the wishlist? You'll have to go through all of your views updating the references to the session.
Instead I would opt for registering your session with your IoC container of choice and injecting it using dependency injection, here is a simple example of how to register the session with StructureMap:
public class WebsiteRegistry : Registry
{
public WebsiteRegistry()
{
this.For<IUserWishlist>().HybridHttpOrThreadLocalScoped().Use(() => GetUserWishlistFromSession());
}
public static IUserWishlist GetUserWishlistFromSession()
{
var session = HttpContext.Current.Session;
if (session["WishList"] != null)
{
return session["WishList"] as IUserWishlist;
}
/* Create new empty session object */
session["WishList"] = new UserWishlist();
return session["WishList"] as IUserWishlist;
}
}
Now you're able to inject your wishlist into your controller and pass the data to your view via a view model. And as you're now programming against an interface instead of an implementation you could easily change how the wishlist is persisted without needing to change any code that references the wishlist.
public class WishlistController : Controller {
private readonly IUserWishlist userWishlist;
public void WishlistController(IUserWishlist userWishlist) {
this.userWishlist= userWishlist;
}
public ActionResult ViewProfile()
{
...
var viewModel = new UserWishlistViewModel {
wishlist = this.userWishlist.GetWishList()
}
...
}
}
I've written a more detailed example up in a blog post that might be of interest which can be read here. I hope this helps!

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.

Global redirect based on logic in ASP.NET MVC3

I am building an ASP.NET MVC3 computer support ticketing portal.
There is a maintenance state, where it is best to forbid the users from interacting with EF/Database, to avoid "collisions" I am currently getting.
I have an IMaintenanceDispatcher that has a boolean property IsOnMaintenance set to true by the business logic, whenever a background logic puts the portal in that state.
I need to redirect client requests to a parking page for the time of maintenance.
Where do I place the logic that will check if the IsOnMaintenance is true, and if so, do a redirect to a URL?
You could put it in an ActionFilterAttribute and apply that attribute to any applicable actions/controllers or globally.
public class IsOnMaintenanceAttribute : ActionFilterAttribute
{
//You'll need to setup your IoC to inject this
public IMaintenanceDispatcher InjectedMaintenanceDispatcher { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
object ticketIdObj;
if (!filterContext.ActionParameters.TryGetValue("ticketId", out ticketIdObj))
return;
//Make sure it exists
if (InjectedMaintenanceDispatcher.IsOnMaintenance(int.parse(ticketIdObj)))
{
var routeValues = new RouteValueDictionary(new {
action = "parkingpage",
controller = "maintenance",
area = "ticket" });
filterContext.Result = new RedirectToRouteResult(routeValues);
return;
}
}
}
Note, your action method parameters needs to contain a variable named ticketId for the filterContext.ActionParameters.TryGetValue to work.
Note: I had assumed that an individual ticket is put into maintenance mode and you were wanting to check for that... but re-reading the question it seems like you want to put the whole area/site on hold. Even with that case, the ActionFilterAttribute example still holds... just not as different.

Resources