How to pass ControllerContext to Attribute - asp.net-mvc

I would like to use information from the ControllerContext or ActionContext from inside the Attribute\Filter constructor. How can i do so? Tried to pass the ControllerContext as a parameter to the attribute from the action but with no success.

public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var controllerContext = filterContext.Controller.ControllerContext;
on action executing. You cannot have the context on construction

[HttpGet]
public ActionResult VideoStream(int id = 0)
{
}
public override void ExecuteResult(ControllerContext context)
{
string routedata = context.RequestContext.RouteData.Values["id"].ToString();
//The File Path
var videoFilePath = HostingEnvironment.MapPath("~/CombineFile/Tanvir.mp4");
//The header information
}

Related

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
{
...
}

ActionExecutingContext ActionParameters gets an object

I have a controller like this
public ActionResult ChangePassword(LG_CRD_PASSWORD_MODIFY_MAP pLG_CRD_PASSWORD_MODIFY_MAP)
{
....
}
And an OnActionExecuting Method
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var param = filterContext.ActionParameters;
}
I'm not being able to extract values from the object parameter
Retrieve object from dictionary:
LG_CRD_PASSWORD_MODIFY_MAP extractedObj = param.Single(x => x.Key == "pLG_CRD_PASSWORD_MODIFY_MAP").value;
For example:
var prop = extractedObj.CURRENT_PASSWORD;

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.

How to disable a global filter in ASP.Net MVC selectively

I have set up a global filter for all my controller actions in which I open and close NHibernate sessions. 95% of these action need some database access, but 5% don't. Is there any easy way to disable this global filter for those 5%. I could go the other way round and decorate only the actions that need the database, but that would be far more work.
You could write a marker attribute:
public class SkipMyGlobalActionFilterAttribute : Attribute
{
}
and then in your global action filter test for the presence of this marker on the action:
public class MyGlobalActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.ActionDescriptor.GetCustomAttributes(typeof(SkipMyGlobalActionFilterAttribute), false).Any())
{
return;
}
// here do whatever you were intending to do
}
}
and then if you want to exclude some action from the global filter simply decorate it with the marker attribute:
[SkipMyGlobalActionFilter]
public ActionResult Index()
{
return View();
}
Though, the accepted answer by Darin Dimitrov is fine and working well but, for me, the simplest and most efficient answer found here.
You just need to add a boolean property to your attribute and check against it, just before your logic begins:
public class DataAccessAttribute: ActionFilterAttribute
{
public bool Disable { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (Disable) return;
// Your original logic for your 95% actions goes here.
}
}
Then at your 5% actions just use it like this:
[DataAccessAttribute(Disable=true)]
public ActionResult Index()
{
return View();
}
In AspNetCore, the accepted answer by #darin-dimitrov can be adapted to work as follows:
First, implement IFilterMetadata on the marker attribute:
public class SkipMyGlobalActionFilterAttribute : Attribute, IFilterMetadata
{
}
Then search the Filters property for this attribute on the ActionExecutingContext:
public class MyGlobalActionFilter : IActionFilter
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.Filters.OfType<SkipMyGlobalActionFilterAttribute>().Any())
{
return;
}
// etc
}
}
At least nowadays, this is quite easy: to exclude all action filters from an action, just add the OverrideActionFiltersAttribute.
There are similar attributes for other filters: OverrideAuthenticationAttribute, OverrideAuthorizationAttribute and OverrideExceptionAttribute.
See also https://www.strathweb.com/2013/06/overriding-filters-in-asp-net-web-api-vnext/
Create a custom Filter Provider. Write a class which will implement IFilterProvider. This IFilterProvider interface has a method GetFilters which returns Filters which needs to be executed.
public class MyFilterProvider : IFilterProvider
{
private readonly List<Func<ControllerContext, object>> filterconditions = new List<Func<ControllerContext, object>>();
public void Add(Func<ControllerContext, object> mycondition)
{
filterconditions.Add(mycondition);
}
public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
return from filtercondition in filterconditions
select filtercondition(controllerContext) into ctrlContext
where ctrlContext!= null
select new Filter(ctrlContext, FilterScope.Global);
}
}
=============================================================================
In Global.asax.cs
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
MyFilterProvider provider = new MyFilterProvider();
provider.Add(d => d.RouteData.Values["action"].ToString() != "SkipFilterAction1 " ? new NHibernateActionFilter() : null);
FilterProviders.Providers.Add(provider);
}
protected void Application_Start()
{
RegisterGlobalFilters(GlobalFilters.Filters);
}
Well, I think I got it working for ASP.NET Core.
Here's the code:
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// Prepare the audit
_parameters = context.ActionArguments;
await next();
if (IsExcluded(context))
{
return;
}
var routeData = context.RouteData;
var controllerName = (string)routeData.Values["controller"];
var actionName = (string)routeData.Values["action"];
// Log action data
var auditEntry = new AuditEntry
{
ActionName = actionName,
EntityType = controllerName,
EntityID = GetEntityId(),
PerformedAt = DateTime.Now,
PersonID = context.HttpContext.Session.GetCurrentUser()?.PersonId.ToString()
};
_auditHandler.DbContext.Audits.Add(auditEntry);
await _auditHandler.DbContext.SaveChangesAsync();
}
private bool IsExcluded(ActionContext context)
{
var controllerActionDescriptor = (Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)context.ActionDescriptor;
return controllerActionDescriptor.ControllerTypeInfo.IsDefined(typeof(ExcludeFromAuditing), false) ||
controllerActionDescriptor.MethodInfo.IsDefined(typeof(ExcludeFromAuditing), false);
}
The relevant code is in the 'IsExcluded' method.
You can change your filter code like this:
public class NHibernateActionFilter : ActionFilterAttribute
{
public IEnumerable<string> ActionsToSkip { get; set; }
public NHibernateActionFilter(params string[] actionsToSkip)
{
ActionsToSkip = actionsToSkip;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (null != ActionsToSkip && ActionsToSkip.Any(a =>
String.Compare(a, filterContext.ActionDescriptor.ActionName, true) == 0))
{
return;
}
//here you code
}
}
And use it:
[NHibernateActionFilter(new[] { "SkipFilterAction1 ", "Action2"})]

Custom model binding issue

In my MVC 3 solution I want to have all Ids in querystring to be crypted. To decrypt URLs I inherited from DefaultModelBinder and overrided BindProperty method:
public class CryptedIdBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.Name.ToLower() == "id")
{
propertyDescriptor.SetValue(bindingContext.Model, CryptoHelper.Decrypt(controllerContext.HttpContext.Request.Form["id"]));
return;
}
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
return;
}
After that I set new DefaultBinder in global.asax on Application_Start:
System.Web.Mvc.ModelBinders.Binders.DefaultBinder = new CryptedIdBinder();
I didn't inherit from IModelBinder because I want to change binding logic only for id fields in solution.
The issue is that BindProperty method is never called. What am I doning wrong?
PS. In order to be sure that I call at least BindModel method I added a peace of this code inside my custom binder, and it was hit by the debugger:
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
return base.BindModel(controllerContext, bindingContext);
}
If your models don't have Id properties of course the BindProperty won't be called. Because it called on the model properties. If I understood your question what you need is to transform each Id named query string parameter. In this case you need a custom value provider instead of a modelbinder. This is good article about the value providers. And it's quite easy to write one:
public class MyValueProviderFacotry : ValueProviderFactory
{
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
return new MyValueProvider(controllerContext);
}
}
public class MyValueProvider : IValueProvider
{
private ControllerContext controllerContext;
public MyValueProvider(ControllerContext controllerContext)
{
this.controllerContext = controllerContext;
}
public bool ContainsPrefix(string prefix)
{
return true;
}
public ValueProviderResult GetValue(string key)
{
if (key.ToLower() == "id")
{
var originalValue = controllerContext.HttpContext.Request.QueryString[key];
var transformedValue = CryptoHelper.Decrypt(orignalValue );
var result = new ValueProviderResult(transformedValue,originalValue,CultureInfo.CurrentCulture);
return result;
}
return null;
}
}
In global.asax:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
ValueProviderFactories.Factories.Insert(4, new MyValueProviderFacotry()); //Its need to be inserted before the QueryStringValueProviderFactory
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}

Resources