MVC Attributes on Controllers and Actions - c#

Is there a way to add an Attribute on the Controller level but not on a specific action. For example say if i had 10 Actions in my Controller and just 1 of those Actions does not require a specific attribute I created.
[MyAttribute]
public class MyController : Controller
{
public ActionResult Action1() {}
public ActionResult Action2() {}
[Remove_MyAttribute]
public ActionResult Action3() {}
}
I could potentially move this Action into another controller (but dont like that) or I could apply the MyAttribute to all actions except from Action3 but just thought if there is an easier way?

I know my answer is a little late (almost four years) to the game, but I came across this question and wanted to share a solution I devised that allows me to do pretty much what the original question wanted to do, in case it helps anyone else in the future.
The solution involves a little gem called AttributeUsage, which allows us to specify an attribute on the controller (and even any base controllers!) and then override (ignore/remove) on individual actions or sub-controllers as needed. They will "cascade" down to where only the most granular attribute actually fires: i.e., they go from least-specific (base controllers), to more-specific (derived controllers), to most-specific (action methods).
Here's how:
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method, Inherited=true, AllowMultiple=false)]
public class MyCustomFilterAttribute : ActionFilterAttribute
{
private MyCustomFilterMode _Mode = MyCustomFilterMode.Respect; // this is the default, so don't always have to specify
public MyCustomFilterAttribute()
{
}
public MyCustomFilterAttribute(MyCustomFilterMode mode)
{
_Mode = mode;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (_Mode == MyCustomFilterMode.Ignore)
{
return;
}
// Otherwise, respect the attribute and work your magic here!
//
//
//
}
}
public enum MyCustomFilterMode
{
Ignore = 0,
Respect = 1
}
(I heard you like attributes, so I put some attributes on the attribute! That's really what makes the magic work here at the very top: Allowing them to inherit/cascade, but only allowing one of them to execute.)
Here's how it is used now:
[MyCustomFilter]
public class MyBaseController : Controller
{
// I am the application's base controller with the filter,
// so any derived controllers will ALSO get the filter (unless they override/Ignore)
}
public class HomeController : MyBaseController
{
// Since I derive from MyBaseController,
// all of my action methods will also get the filter,
// unless they specify otherwise!
public ActionResult FilteredAction1...
public ActionResult FilteredAction2...
[MyCustomFilter(Ignore)]
public ActionResult MyIgnoredAction... // I am ignoring the filter!
}
[MyCustomFilter(Ignore)]
public class SomeSpecialCaseController : MyBaseController
{
// Even though I also derive from MyBaseController, I can choose
// to "opt out" and indicate for everything to be ignored
public ActionResult IgnoredAction1...
public ActionResult IgnoredAction2...
// Whoops! I guess I do need the filter on just one little method here:
[MyCustomFilter]
public ActionResult FilteredAction1...
}
I hope this compiles, I yanked it from some similar code and did a little search-and-replace on it so it may not be perfect.

You have to override/extend the default attribute and add a custom constructor to allow exclusion. Or you can create your custom attribute for exclusion (in your example is the [Remove_MyAttribute]).

Johannes gave the correct solution and here is how I coded it... hope it helps other people.
[MyFilter("MyAction")]
public class HomeController : Controller
{
public ActionResult Action1...
public ActionResult Action2...
public ActionResult MyAction...
}
public class CompressFilter : ActionFilterAttribute
{
private IList _ExcludeActions = null;
public CompressFilter()
{
_ExcludeActions = new List();
}
public CompressFilter(string excludeActions)
{
_ExcludeActions = new List(excludeActions.Split(','));
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpRequestBase request = filterContext.HttpContext.Request;
string currentActionName = (string)filterContext.RouteData.Values["action"];
if (_ExcludeActions.Contains(currentActionName))
return;
...
}

You could exclude a specific action by passing it to the main attribute:
[MyAttribute(Exclude="Action3")]
EDIT
My example was from the head (as you can see the following is VB.NET, maybe that's where it went wrong), this is how I implemented:
<Models.MyAttribute(Exclude:="Action3")> _
Public Class MyController
Inherits System.Web.Mvc.Controller
End Class

The usual pattern for what you are trying to do is to have and attribute with a boolean parameter that indicates if the attribute is applied or not.
Ex:
[ComVisible] which is equivalent with [ComVisible(true)]
or
[ComVisible(false)]
inf your case you would have:
[MyAttribute] // defaults to true
and
[MyAttribute(false)] for applying the attribute on excluded members

Related

Should same methods from different controllers be moved to a CommonController?

I have two controllers that have few same methods:
public class Controller1 : Controller
{
private readonly ITestBL bl;
public Controller1(ITestBL bl)
{
this.bl= bl;
}
[HttpGet]
public ActionResult Method1(string data)
{
using (bl)
{
var res = ...
return Json(res, JsonRequestBehavior.AllowGet);
}
}
[HttpGet]
public ActionResult Method2(string data, int data2)
{
using (bl)
{
var res = ...
return Json(res, JsonRequestBehavior.AllowGet);
}
}
// other methods
}
And the second controller also has those two methods.
Should I create some common controller to keep those methods? So, it will look like this:
public abstract class CommonController: Controller
{
private readonly ITestBL bl;
protected Controller1(ITestBL bl)
{
this.bl= bl;
}
[HttpGet]
public ActionResult Method1(string data)
{
using (bl)
{
var res = ...
return Json(res, JsonRequestBehavior.AllowGet);
}
}
[HttpGet]
public ActionResult Method2(string data, int data2)
{
using (bl)
{
var res = ...
return Json(res, JsonRequestBehavior.AllowGet);
}
}
}
And my Controller1 and Controller2 will be:
public class Controller1 : CommonController
{
private readonly ITestBL bl;
public Controller1(ITestBL bl)
:base(bl)
{
}
// methods
}
Is that the proper way to do that? Do I miss anything or is there a better way?
Should same methods from different controllers be moved to a CommonController?
Yes and you should not use Inheritance. I'm sure there are plenty of people who may disagree, however your example is extremely generic and gives very poor context there is no good reason all controllers need the same code (Inheritance or not). Your questions context has no reason for it to be the case in the OOP realm of Has A vs Is A (Excerpt below).
A House is a Building (inheritance);
A House has a Room (composition);
What it appears you are doing is neither of these.
If your interface was IVehicleEngine and your controllers were FerarriVehicleController and FordVehicleController now it makes sense in a context. In this case each controller should use inheritance, it makes sense.
In my own humble opinions, inheriting from your own controller can be quite difficult in multiple terms. First, it's not intuitive; that is it will become tribal knowledge because it replaces a normal convention that most programmers adhere to (that is deriving from the base MVC controller). Secondly, I've seen it become the one place that everyone decides to add code to (God Object) even though it may not be applicable from some controllers. Thirdly, it makes it difficult to reuse url's that make sense for the derived type but not the base type (/search?searchFor=). There are a number of other considerations that are very specific to MVC because of it's exposure to the web (security etc etc).
Depending on implementation you may also experience difficulty in determining which URL to use under what circumstances.
Is /Controller1/Method1/Data/1 the same as /Controller2/Method1/Data/1 but different from /Controller3/Method1/Data/1? If they are all the same or some are the same and some are different then there is most likely something wrong with the architecture.
Nothing wrong with inheriting from a base controller. As it adheres to the DRY principle. I would go with :
public abstract class CommonController: Controller
{
protected readonly ITestBL bl;
protected Controller1(ITestBL bl)
{
this.bl= bl;
}
[HttpGet]
public virtual ActionResult Method1(string data)
{
var res = ...
return Json(res, JsonRequestBehavior.AllowGet);
}
[HttpGet]
public virtual ActionResult Method2(string data, int data2)
{
var res = ...
return Json(res, JsonRequestBehavior.AllowGet);
}
}
Main differences being.
I removed "using". Like the others have said it's best to let your DI framework decide when to dispose of the injected class.
Make the action results virtual. The inheriting controllers may have the same requirements now, but there is no guarantee that they will stay that way in the future. So declaring them as virtual allows for future changes in scope/requirements as they can be overridden.
I made the injected class "protected" rather then "private" as other methods in both inheriting controllers may need it too.

Potential WebApi Bug Overriding routes create multiple Endpoints

I think I've discovered a webapi bug.
This is a simplified version of the problem I have this Controller, whose routes get list is suppose to link up to the employeeId
[RoutePrefix("/divisions/{parentId}/employees/{employeeId}/dependent")]
public class EmployeeDependentController : ParentBasedListController<EmployeeDependentDTO, GlobalEntityKey<IEmployee>>
{
[HttpGet]
[ReadRoute("")]
public override EmployeeDependentDTO[] GetList(GlobalEntityKey<IEmployee> employeeId, bool keysOnly = false)
{
return base.GetList(employeeId, keysOnly);
}
protected override EmployeeDependentDTO[] GetListImp(GlobalEntityKey<IEmployee> employeeKey)
{
return GlobalFactory<IEmployeeDependentService>.Instance.GetList(employeeKey);
}
}
However it's inheriting from a base class whose parameter name links up to the parentId normally
public abstract class ParentBasedListController<TEntityDTOHeader, TEntityDTOKey, TParentEntityKey> : ApiController
where TParentEntityKey : IKey
where TEntityDTOHeader : TEntityDTOKey
{
#region Public Methods
[HttpGet]
[ReadRoute("")]
public virtual TEntityDTOKey[] GetList(TParentEntityKey parentId, bool keysOnly = false)
{
if (false == keysOnly)
{
return this.GetListImp(parentId).Cast<TEntityDTOKey>().ToArray();
}
return this.GetKeyListImp(parentId);
}
#endregion
#region Protected Methods
protected abstract TEntityDTOHeader[] GetListImp(TParentEntityKey parentKey);
#endregion
}
When I Override the get list method, webapi returns an error for duplicate matching actions. What's even more interesting is that i've tried this with the new keyword which should hide that method in both case I receive this error.
Why is this happening? is this a bug in webapi?, I understand that they are using reflection for the controllers but they should be able to determine if a method was overridden and use the correct one.
I have several controllers tied to this route and several controller inheriting from this base class both in which would be a hassle to change, What is the best work around for this?

How do I inherit from an ActionFilter attribute in a assembly in the same project but not directly referenced?

What I am asking my not even be possible (I am skeptical) but I have been tasked to do this exact thing so please if its not possible or dumb please explain why.
I have a base project that contains an ActionFilter attribute that overrides OnActionExecuting. It does nothing as it is supposed to be a base class to inherit from.
namespace mybaseproject
public class BaseActionAttribute : ActionFilterAttribute
{
public string ActionName {get;set;}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
}
}
I have a second project that references 'mybaseproject' and uses BaseActionAttribute.
namesapce mysecondproject
public class MyController : Controller
{
[BaseAction(ActionName="Index")]
public ActionResult Index()
{
//do stuff
}
}
I have a third project that references both 'mybaseproject' and 'mysecondproject'. It has an ActionFilterAttribute that inherits from the base one and has all of the real code to process.
using mybaseproject;
namespace mythirdproject
public class DerivedActionAttribute : BaseActionAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// do all the real stuff
base.OnActionExecuting(filterContext);
}
}
What the desired effect is for the BaseActionAttribute in mysecondproject to use the code in DerivedActionAttribute of mythirdproject. I don't see how this can work because mysecondproject has no reference to mythirdproject. They just reside in the solution. I have tried using Authorize attribute as well but my breakpoint never gets hit in the derived attribute when I call my action in the second project.
This was how I was told to tackle the problem. Either I misunderstood or am taking the wrong approach, or this is just not possible.
Which one? Thanks ahead for listening.

Create controller base class (partial)

Since my #html.render action crashes my dev and prod servers i have to use partials(crap).
I tried creating public partial controller{} class so i can set needed data for all my views but i am having no luck (everything breaks).
I am coming from LAMP cakePHP background and really need simplicity.
I need to know how to create a partial base controller(that doesnt override the regular base controller) and how to access multiple models from the class.
Thank you!
public class BaseController: Controller
{
public override OnActionExecuting(...) { ... }
public override OnActionExecuted(... context)
{
if (context.Result is ViewResult)
((ViewResult)context.Result).ViewData["mycommondata"] = data;
}
...
}
public class MyController1: BaseController
{
}
I.e. just derive from your new base controller class.
However I'd suggest you to ask here why your RenderPartial "crashes" - since it can be a better way for you, and it obviously shouldn't crash.
better way to create base controller
public class Controller : System.Web.Mvc.Controller
{
public shipsEntities db = new shipsEntities();
public Controller()
{
ViewData["ships"] = db.ships.ToList();
}
}
that way the rest of controllers follow regular convention
public class MyController : Controller

C# Centralizing repeating VIewData in MVC

When a user log in into my application i want to show his name throughout the whole application. I am using the asp.net MVC framework. But what i don't want is that is have to put in every controller something like:
ViewData["User"] = Session["User"];
This because you may not repeat yourself. (I believe this is the DRY [Don't Repeat Yourself] principle of OO programming.)
The ViewData["User"] is on my masterpage. So my question is, what is a neat way to handle my ViewData["User"] on one place?
You can do this fairly easily in either a controller base-class, or an action-filter that is applied to the controllers/actions. In either case, you get the chance to touch the request before (or after) the action does - so you can add this functionality there.
For example:
public class UserInfoAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(
ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
filterContext.Controller.ViewData["user"] = "Foo";
}
}
...
[HandleError, UserInfo]
public class HomeController : Controller
{...}
(can also be used at the action (method) level)
or with a common base-class:
public abstract class ControllerBase : Controller
{
protected override void OnActionExecuting(
ActionExecutingContext filterContext)
{
ViewData["user"] = "Bar";
base.OnActionExecuting(filterContext);
}
}
[HandleError]
public class HomeController : ControllerBase
{...}
It's been a year, but I've just stumbled across this question and I believe there's a better answer.
Jimmy Bogard describes the solution described in the accepted answer as an anti-pattern and offers a better solution involving RenderAction: http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/06/18/the-filter-viewdata-anti-pattern.aspx
Another method for providing persistent model data through out your entire application is by overriding the DefaultFactoryController with your custom one. In your CustomerFactoryController, you would hydrate the ViewBag with the model you are wanting to persist.
Create a base class for your models with UserName property:
public abstract class ModelBase
{
public string UserName { get; set; }
}
Create a base class for you controllers and override it's OnActionExecuted method. Within it check if model is derrived from BaseModel and if so, set it's UserName property.
public class ControllerBase : Controller
{
protected override void OnActionExecuted(
ActionExecutedContext filterContext)
{
var modelBase = ViewData.Model as ModelBase;
if (modelBase != null)
{
modelBase.UserName = "foo";
}
base.OnActionExecuted(filterContext);
}
}
Then you will be able to display user's UserName in the view like this:
<%= Html.Encode(Model.UserName) %>
See also:
ASP.NET MVC Best Practices, Tips and Tricks

Categories

Resources