At what point in Controller execution is TempData populated - asp.net-mvc

I have a base class for my controllers. In the constructor of the base class I was trying to populate a ViewBag property from TempData. However it seems that TempData is not populated at that point, nor is it in the OnBeginExecute method.
I need to populate this ViewBag property in the base class, as all controllers need the same variable (it's a redirection message).
Which override of Controller in my base class can I use to do this?

TempData as well as any HttpContext related stuff is not available in the controller constructor. You can use them starting from the Initialize method. So if you need to populate them in a global manner for a controller either override this method or write a custom action filter and decorate your controller with it:
public class HomeController: Controller
{
protected override void Initialize(RequestContext requestContext)
{
base.Initialize(requestContext);
// now you can access the HttpContext
}
...
}

Take a look at BeginExecuteCore:
protected override IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
{
// TempData is not populated here
var result = base.BeginExecuteCore(callback, state);
// TempData is populated here
return result;
}

Related

how to unit test for session variable in controller in mvc

I am unit-testing my controller.
In one of my controller methods I am setting Session variables:
public void Index()
{ Session["foo"] = "bar";
return View();
}
How can I unit-test this? The problem is that the Session property is null when testing. Injecting is not possible because the Session property is readonly.
I don't want to use any third-party tool or mocking.
Simply dont use things like Session["foo"] in your controller methods. Best practice is keep action methods unaware of any context-like global objects. Everything your action method needs should be given to her in form of arguments. Note that built-in mechanism of model binding works exactly like that - you dont use Request.Form[], you let "somebody behind the scene" pass it to your action as argument.
Now for the session you can do the same - write you very simple ValueProvider which will know how to recognize arguments you want to fill from session, and you are done. In production your actions will work with session, in test you cant simply pass them any values you want as arguments.
For inspiration look at this http://www.prideparrot.com/blog/archive/2012/7/how_to_create_a_custom_session_value_provider
Injecting is not possible because the Session property is readonly.
This means you cannot use setter injection, but could you use constructor injection, ie add a constructor for your controller that is something like:
MyController(Session session)
{
m_session = session;
// then call your main constructor
}
Session getSession()
{
return m_session;
}
You can then use this separate constructor during testing.
I agree with #rouen. do not directly use Session["foo"]. But I think having ValueProvider ans might not be a practical solution, as we only store very few variables, and these values may be and most likely not ur full model.
So my approach is something similar to what Vic Smith suggests but a much more IOC (and Mock) friendly.
I would create a provider (i.e a service) to retrieve the session variables
public class SessionVariableProvider : ISessionVariableProvider
{
public object GetSessionValue(string key)
{
if (!HttpContext.Current.Session.IsNewSession
&& HttpContext.Current.Session[key] != null)
{
return HttpContext.Current.Session[key];
}
throw new ArgumentNullException(key);
}
public void SetSessionValue(string key, object value)
{
HttpContext.Current.Session[key] = value;
}
}
public interface ISessionVariableProvider
{
object GetSessionValue(string key);
void SetSessionValue(string key, object value);
}
Modify your Controller expect ISessionVariableProvider as a parameter.
public class TestController: Controller
{
protected readonly ISessionVariableProvider _sessionVariableProvider;
protected InowiaControllerBase(ISessionVariableProvider sessionVariableProvider)
{
Guard.ArgumentNotNull(sessionVariableProvider, "sessionVariableProvider");
this._sessionVariableProvider = sessionVariableProvider;
}
public ActionResult Index()
{
_sessionVariableProvider.SetSessionValue("foo", "bar");
var foo2 = (string)_sessionVariableProvider.GetSessionValue("foo2");
return View();
}
}
when testing create your own test implementation of ISessionVariableProvider and pass it to the controller.

Get instance of ActionFilterAttribute in the method

I am newbie in ASP.NET MVC platform and I faced with the following problem.
I am using ActionFilterAttribute to do some routine work before and after action method run. The problems is that I need to get instance of the attribute in action method to read some properties which was set in OnActionExecuting method. For example
public class SomeController : Controller{
public SomeController(){ }
[Some]
public ActionResult Index(){
SomeModel = someRepository.GetSomeModel();
//get instance of some attribute and read SomeProperty
return View(SomeModel);
}
}
public class SomeAttribute : ActionFilterAttribute{
public int SomeProperty { get; set; }
public SomeAttribute(){ }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var parameters = filterContext.ActionParameters;
//Here to set SomeProperty depends on parameters
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
//do some work
}
}
Any ideas?
Filter attributes must be designed to be thread-safe. The framework makes no guarantees that a single instance of your filter attribute will only service one request at a time. Given this, you cannot mutate attribute instance state from within the OnActionExecuting / OnActionExecuted methods.
Consider one of these as alternatives:
Use HttpContext.Items to store the value in OnActionExecuting, then read it from the action method. You can access HttpContext via the filterContext parameter passed to OnActionExecuting.
Put the property on the controller instead of the attribute, then have the OnActionExecuting method cast the controller to SomeController and set the property directly from within that method. This will work since the framework does by default guarantee that controller instances are transient; a single controller instance will never service more than one request.
Option 1: Your ActionFilter can add information to the ViewModel, e.g.
filterContext.Controller.ViewData["YourKey"] = "Value to add";
Option 2: You can put code in your base Controller class that finds all the attributes that have been applied to the method that is executing, and you can put them in a member variable that the Action method can then use.
e.g.
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var attrs = filterContext.ActionDescriptor.GetCustomAttributes(true).OfType<Some>();
...
}
Edit: And as others have noted, trying to mutate the attribute isn't going to work.
Sorry, I do not believe this is possible. Since the value of SomeProperty must be based on parameters sent into the constructor of the attribute, it must be easy to calculate. I would suggest adding some static methods to get the value from within the action.

HttpContext.Current.User is null in ControllerBase(asp.net mvc)

I have a ControllerBase class in an ASP.NET MVC Application. The other controllers inherit from ControllerBase.
I want to access HttpContext.User.Identity.Name, but HttpContext is null. What's the matter?
public ControllerBase()
{
var dataManager=new DataManager();
if (HttpContext.User.Identity.IsAuthenticated) // throws error
{
ViewData["assets"] = ud.BalanceFreeze + ud.Balance + ud.BalanceRealty;
ViewData["onaccount"] = ud.Balance;
ViewData["pending"] = ud.BalanceFreeze;
ViewData["inrealty"] = ud.BalanceRealty;
}
Try adding your code to this event in your ControllerBase:
protected override void Initialize(RequestContext requestContext){
}
Your controller gets constructed before the HttpContext has been set by ASP.NET. Like Nik says, you need to put this code into an overridden method in your class.
I would also point out that depending on HttpContext directly will make it impossible to perform unit testing on any of your controllers that extend this class. This is why many of the methods (like the Execute method) in the ControllerBase class take a RequestContext as an argument. You can say:
protected override void Execute(System.Web.Routing.RequestContext requestContext)
{
var currentUser = requestContext.HttpContext.User;
...
}
... which makes it possible to create and execute your controllers with "fake" contexts for unit testing purposes.

asp.net mvc can't access cookie data in base controller

For every page requested, I need to check a cookie or create it if it's not there.
If the cookie is there, I need to load up some info from the DB based on the contents of that cookie.
To do this I have created a base controller called AppController that my other controllers inherit from.
then I have something like this (so that the CurrentSessionValues object is available to all my controllers):
public MySession CurrentSessionValues;
public ApplicationController()
{
if (Request.Cookies["MySiteCookie"] == null)
{
// create new Record in DB
CurrentSessionValues = CreateMySession();
HttpCookie cookie = new HttpCookie("MySiteCookie");
cookie.Value = CurrentSessionValues.SessionID.ToString;
Response.SetCookie(cookie);
}
else
{
// use the value in MySiteCookie to get values from the DB
// e.g. logged in user id, cart id, etc
}
}
When I run this, I get this error in default.aspx:
An error occurred while creating a
controller of type
'Mvc_Learn.Controllers.HomeController'.
If the controller doesn't have a
controller factory, ensure that it has
a parameterless public constructor.
It breaks on Request.Cookies["MySiteCookie"]
Should I be doing this logic in some other way or another place?
Trick is that you don't have context in the constructor necessarily. Rather, you should override the Initialize method:
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
// DO NOT forget to call the base
base.Initialize(requestContext);
//check request context for cookie and do your thang.
}
PS: for posterity, I should note why there is an error. The key part of the exception info is that an error took place while creating the controller, the parameterless constructor bit is a red herring in this case. The error which took place was a null reference exception to HttpContext.
Check that HomeController has a parameterless public constructor, and check that the parent constructor ApplicationController() is being called.

Returning an MVC ActionResult before the specific controller method is called

I have a base controller class from which my other controllers are inherited
public abstract class BaseController : Controller
{
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
base.Initialize(requestContext);
...
}
}
During initialization I'm doing some setup, and there are a few cases where I'd want to short circuit the execution, jumping directly to the return of the ActionResult, skipping the execution of the actual Action method entirely. Something along these lines
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
base.Initialize(requestContext);
if(specialCase)
{
ViewData[...] = specialCaseInformation;
return View("~/Shared/SpecialCase.aspx");
}
}
The intention would be to skip whatever ActionResult method was going to be called and replace it with my global special case page. But I don't think Initialize was meant for this.
What I think I need to do is create a seperate ActionFilterAttribute class, override the OnActionExecuting method, and if the specialCase comes up, construct a ViewResult object and assign it to the filterContext.Result property.
Am I going in the right direction with this, or should I be doing this differently?
Yes, an ActionFilterAttribute is exactly the right way. Look at HandleErrorAttribute.cs for an example.
Initialize is not the right way, as you say.

Resources