How to set values to ViewBag in ActionFilterAttribute ASP MVC 5? - asp.net-mvc

Hello I would like to create my custom ActionFilterAttribute for each controller in my application, this attribute should set some ViewBag values. Is ActionFilterAttribute would be fine for it and how to get access to viewbag in ActionFilterAttribute ?

You can do like this
public class SomeMsgAttribute : FilterAttribute, IResultFilter
{
public void OnResultExecuted(ResultExecutedContext filterContext)
{
}
public void OnResultExecuting(ResultExecutingContext filterContext)
{
filterContext.Controller.ViewBag.Msg= "Hello";
}
}
Using:
[SomeMsg]
public ActionResult Index()
{
return View();
}

try this
public class CustomFilterAttribute : ActionFilterAttribute
{
public override void
OnActionExecuting(ActionExecutingContext filterContext)
{
// get the view bag
var viewBag = filterContext.Controller.ViewBag;
// set the viewbag values
viewBag.CustomValue = "CustomValue";
}
}

For ASP.NET Core you can do the following
public class SomeFilterAttribute : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
Controller controller = context.Controller as Controller;
controller.ViewBag.CustomValue = customVal;
controller.ViewData["CustomValue "] = customVal;
controller.TempData["CustomValue "] = customVal;
}
}
Then from the controller
[TypeFilter(typeof(ValidateAppFeatureEnabled))]
public IActionResult Index()
{
var foo = ViewBag.CustomValue;
var bar = (type)ViewData["CustomValue"];
var buzz = (type)TempData["CustomValue"];
// Whatever else you need to do
return View();
}

To transfer data from a different controller action
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
EmployeeTrackingSystemAndMISEntities db = new EmployeeTrackingSystemAndMISEntities();
var UserCookie = filterContext.HttpContext.Request.Cookies["UserUniqueID"];
RouteValueDictionary redirectTargetDictionary = new RouteValueDictionary();
redirectTargetDictionary.Add("action", "UserLogIn");
redirectTargetDictionary.Add("controller", "Login");
var TempData = filterContext.Controller.TempData;
TempData["Status"] = "Please log in as Admin";
filterContext.Result = new RedirectToRouteResult(redirectTargetDictionary);
}

Related

Pass value from ActionFilterAttribute to controller

I have the following base controller with a string variable
public abstract class BaseController:Controller
{
string encryptedSessionGuid;
}
All other controller derives from base controller and ActionMethod has a custom ActionFilterAttribute CheckQueryString-
public class SampleController : BaseController
{
[CheckQueryString(new string[] {"sid"})]
public ActionResult SampleMethod()
{
return View();
}
}
Here is my custom attribute. It sends query string value to view. But I would like to send it base controller variable encryptedSessionGuid also.
public class CheckQueryString : ActionFilterAttribute
{
string[] keys;
public CheckQueryString(string[] Keys) { keys = Keys; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpContext ctx = HttpContext.Current;
foreach (var key in keys)
{
if (ctx.Request.QueryString[key] == null)
{
filterContext.Result = new RedirectResult(BulkSmsApplication.GlobalConfig.BaseUrl);
return;
}
else
{
string value = ctx.Request.QueryString[key];
if (string.IsNullOrEmpty(value))
{
filterContext.Result = new RedirectResult(BulkSmsApplication.GlobalConfig.BaseUrl);
return;
}
else
{
var viewBag = filterContext.Controller.ViewData;
viewBag[key] = value;
}
}
}
base.OnActionExecuting(filterContext);
}
}
How can it be done?

Unit testing logic in controller OnActionExecuting event

There is a custom logic to set the page title in my controller's OnActionExecuting event which sets default value for title if it is not set using an attribute on action methods:
[PageTitle("Overriden page title")]
public ActionResult About()
{
return View();
}
public ActionResult Error()
{
return View();
}
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
//Page title
var title = filterContext.ActionDescriptor.GetCustomAttributes(typeof(PageTitleAttribute), false);
if (title.Length == 1)
ViewBag.Title = ((PageTitleAttribute)(title[0])).Parameter;
else
ViewBag.Title = "Default Website Title";
}
How can I unit test this functionality?
This is what I ended up doing (might be helpful for anyone facing similar problem).
1) I split up the code in the controller to below:
public class BaseController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
SetupMetadata(filterContext);
base.OnActionExecuting(filterContext);
}
public virtual void SetupMetadata(ActionExecutingContext filterContext)
{
//Page title
var title = filterContext.ActionDescriptor.GetCustomAttributes(typeof(PageTitleAttribute), false);
if (title.Length == 1)
ViewBag.Title = ((PageTitleAttribute)(title[0])).Parameter;
else
ViewBag.Title = "Default Page Title";
}
}
2) Deriving my HomeController from the basecontroller.
3) And then unit tested it using:
[TestClass]
public class BaseControllerTests
{
[TestMethod]
public void OnActionExecuting_should_return_attribute_value_when_set()
{
var ctx = new Mock<ActionExecutingContext>();
var controller = new HomeController();
ctx.Setup(c => c.Controller).Returns(controller);
ctx.Setup(c => c.ActionDescriptor.GetCustomAttributes(typeof(PageTitleAttribute), false)).Returns(new object[] { new PageTitleAttribute("Overriden Title") });
controller.SetupMetadata(ctx.Object);
Assert.AreEqual("Overriden Title", controller.ViewBag.Title);
}
[TestMethod]
public void OnActionExecuting_should_return_default_attribute_values_if_attributes_are_missing()
{
var ctx = new Mock<ActionExecutingContext>();
var controller = new HomeController();
ctx.Setup(c => c.Controller).Returns(controller);
ctx.Setup(c => c.ActionDescriptor.GetCustomAttributes(typeof(PageTitleAttribute), false)).Returns(new object[0]);
controller.SetupMetadata(ctx.Object);
Assert.AreEqual("Default Page Title", controller.ViewBag.Title);
}
}

Can I no longer pass ViewData to _Layout.cshtml in MVC Core 1.0?

I'm trying to pass a URL for a background image to my _Layout.cshtml,
public HomeController()
{
this.ViewData["BackgroundImage"] = "1920w/Stipula_fountain_pen.jpg";
}
and
<body style="background-image: url(#(string.Format("assets/images/{0}", ViewData["BackgroundImage"])))">
...
</body>
but ViewData is always empty inside _Layout.cshtml. Is that working as intended? I'd rather not go down the BaseViewModel/BaseController route as that feels like overkill.
EDIT: It seems as if ViewData set in the constructor isn't actually used, because once an action is executing the collection is empty. If I set ViewData inside the action then that data is passed on to _Layout.cshtml - feels like a bug to me.
You can use an action filter to set ViewData for all controller actions:
public class SetBackgroundUrlAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
var result = filterContext.Result as ViewResult;
if (result != null)
{
result.ViewData["BackgroundImage"] = "1920w/Stipula_fountain_pen.jpg";
}
}
}
[SetBackgroundUrl]
public HomeController()
{
}
Or just override OnActionExecuted method of the controller:
public override void OnActionExecuted(ActionExecutedContext context)
{
base.OnActionExecuted(context);
var result = context.Result as ViewResult;
if (result != null)
{
result.ViewData["BackgroundImage"] = "1920w/Stipula_fountain_pen.jpg";
}
}
Expanding on adem caglin's answer I went with this filter attribute, which can take an arbitrary URL:
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method, AllowMultiple = false)]
public class SetBackgroundUrlAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
if (!string.IsNullOrWhiteSpace(this.Url))
{
var result = filterContext.Result as ViewResult;
if (result != null)
result.ViewData["BackgroundImage"] = this.Url;
}
}
public string Url { get; set; }
}
and is used like so:
[SetBackgroundUrl(Url = "1920w/Stipula_fountain_pen.jpg")]
public class HomeController : Controller
{
...
}

How to write an action filter for all controllers

Here is a sample action filter. We know that when we write an action filter then we need to decorate the controller with an attribute like this, to use it for any controller.
I like to know whether there is any way to write an action filter which will work for all controllers in way that I do not need to decorate all the controllers with an action filter attribute. Any ideas?
[LogActionFilter]
public class HomeController : Controller
{}
public class LogActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
Log("OnActionExecuting", filterContext.RouteData);
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
Log("OnActionExecuted", filterContext.RouteData);
}
private void Log(string methodName, RouteData routeData)
{
var controllerName = routeData.Values["controller"];
var actionName = routeData.Values["action"];
var message = String.Format("{0} controller:{1} action:{2}", methodName, controllerName, actionName);
Debug.WriteLine(message, "Action Filter Log");
}
}
public class LogActionFilterAttribute : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext filterContext)
{
Log("OnActionExecuted", filterContext.RouteData);
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
Log("OnActionExecuting", filterContext.RouteData);
}
private void Log(string methodName, RouteData routeData)
{
var controllerName = routeData.Values["controller"];
var actionName = routeData.Values["action"];
var message = String.Format("{0} controller:{1} action:{2}", methodName, controllerName, actionName);
Debug.WriteLine(message, "Action Filter Log");
}
}
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
GlobalFilters.Filters.Add(new LogActionFilterAttribute());
}
}
For your scenario you can just make a Custom BaseController and put your [LogActionFilter] attribute on Custom Basecontroller and inherit all your Controllers from Custom Basecontroller as shown below :
[LogActionFilter]
public class MyBaseController : Controller
{
}
public class MyOtherController : MyBaseController //<----instead of using Controller as base use MyBaseController as base class
{
public ActionResult Index()
{
// ...
}
}
The advantage of this approach is that you have to put your custom [LogActionFilter] attribute only at one place i.e. only on Custom BaseController.
If you're already subclassing from a base controller, you don't need a filter attribute or to register anything. You can just override the desired methods, e.g.,
public class BaseController : Controller {
protected override void OnActionExecuting(ActionExecutingContext filterContext) {
ViewBag.SomeValue = "glorp frob blizz nunk";
// Access the controller, parameters, querystring, etc. from the filterContext
base.OnActionExecuting(filterContext);
}
}
Then, every controller that subclasses from it will run that method:
public sealed class GlorpController : BaseController {
public ActionResult Index() => View();
}
The view will quite happily see the ViewBag.SomeValue value.

ASP.NET MVC ActionFilter parameter binding

If you have a model-bound parameter in an action method, how can you get to that parameter in an action filter?
[MyActionFilter]
public ActionResult Edit(Car myCar)
{
...
}
public class MyActionFilterAttribute : ActionFilterAttribute
{
public void OnActionExecuted(ActionExecutedContext filterContext)
{
//I want to access myCar here
}
}
Is there anyway to get myCar without going through the Form variables?
Not sure about OnActionExecuted but you can do it in OnActionExecuting:
public class MyActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// I want to access myCar here
if(filterContext.ActionParameters.ContainsKey("myCar"))
{
var myCar = filterContext.ActionParameters["myCar"] as Car;
if(myCar != null)
{
// You can access myCar here
}
}
}
}

Resources