class MyBaseController : AsyncController
{
// ...
}
class SimpleController : MyBaseController
{
public ActionResult MySyncAction()
{
}
}
I ALWAYS have to do one thing, OperationX(). This is a long standing operation for ALL my requests. It's values are different based on many things, but that's a technical detail.
I'd like to do that in an ASync fashion, and then call the controller's action method in the regular way. We have a ton of code in the SimpleController world, and we have hundreds of controllers and routes, etc, so it's no-go to convert all this code into ASync.
The quesiton, therefore is, how do I intercept an action coming to MyBaseController's MySyncAction and then invoke MyBaseController's Async action ALWAYS, and then only on completion, of the async action in the basecontroller, do I then call the MySyncAction of SimplerController.
Is this possible, what's the best way?
If you only want to call the SimpleController's MySyncAction after the BaseController, then couldn't this be done using a FilterAttribute instead?
Here is an example of a custom filter attribute that does some logging...
http://www.asp.net/mvc/tutorials/understanding-action-filters-cs
[MyFilterAttribute]
class SimpleController : AsyncController
{
public ActionResult MySyncAction()
{
}
}
Related
I would like to know if it is best to use One controller for multiple views that load approximately the same data or use one controller for every view.
And if it is possible to use the controller like this :
[Route("api/[controller]")]
public class MultiplePagesController : Controller
{
[HttpGet]
public async Task<IActionResult> GetA()
{ ... return viewA }
[HttpGet]
public async Task<IActionResult> GetB()
{ ... return viewB }
}
And in my serviceViewA.ts :
getA() {
return this.http.get<InterfaceA>(`${this.baseUrl}/api/multiplepages/`);
}
My serviceViewB.ts :
getB() {
return this.http.get<InterfaceB>(`${this.baseUrl}/api/multiplepages/`);
}
Since let's say the viewA returns data of books and the viewB returns data of books and computers.
I am using MVC .NET CORE
It's in fact a great idea to use one controller (and method) if it does exactly the same thing, you reduce code this way, makes it easy to maintain and modify in just one place rather than all over the project. Think of it as a container for your functions. If you have a Book controller, you put all methods related to books there, same for User, you would put all user methods there.
As for your second question, that goes against polymorphism. Your function signatures are exactly the same, how will it be distinguished? You'll need to give it a different name or accept a parameter (different from the other function or different data type).
Worth noting that you can name your function differently to return views.
For example:
public async Task<IActionResult> GetViewA(){ // ... }
public async Task<IActionResult> GetViewB(){ // ... }
You'll call those views by entering: /Controller/GetViewB as your path.
I would like to call the MainController.GetData() action from a few different URLs without a bunch of copied/pasted code. Is there a preferred way people tackle this? Seems like I might be able to do it with routing as well. I would just like to reuse the view and action since all that code would be the same if I made a version for the GetMyData() action.
**Example urls**
*/main/getdata
/other/getmydata
/diffferent/getothersdata?userid=3
public ActionResult MainController::GetData()
{
var data = GetData();
return View(collection);
}
public ActionResult OtherController::GetMyData()
{
var userId = GetCurrentUserId();
var data = GetData(userId);
return View("../main/getdata", collection);
}
Although controllers look like simple classes, their behavior inside the MVC framework is more specialized. You can't, or perhaps it's better to say you shouldn't, just call an action from one in an action for another, as if it's just any old method. Mostly, this is because controllers have context, and you have to instantiate them and set them up just right so that everything works. That's non-trivial to do inside of an action method and it's going to make your code ugly as hell.
You have two choices really. If you just want the result of the action, the best method is to utilize HttpClient and actually submit an HTTP request for it, just like any other request that would activate it.
However, based on your problem description, option two is probably more appropriate. You can create a base class and then inherit from that. For example, if you know two controllers are both going to need GetData, you can do something like:
public abstract class BaseController : Controller
{
protected IEnumerable<Data> QueryData(int? userId = null)
{
...
}
}
public class MainController : BaseController
{
public ActionResult GetData()
{
var data = QueryData();
return View(data);
}
}
public class OtherController : BaseController
{
public ActionResult GetMyData()
{
var userId = GetCurrentUserId();
var data = QueryData(userId);
return View(data);
}
}
In other words, you factor out the common functionality into something both the actions on both derived controllers can use.
This is all you needed for this scenario, but you can also implement entire actions on the base controller. For example, if you added a Foo action to BaseController, both MainController and OtherController would then responds to requests for Foo, without having to actually explicitly define that. You can then also override these actions if you need to, as well.
I basically want to check if the session is still valid for every GET and POST request in my application, however, I don't really want to keep copying and pasting the same code in to every Action Method, I was thinking of using a Base Controller so I can inherit the usage or a static helper controller class (if this can be done??). Are either of these ways the best (or even correct) approach to take?
Example of code
[HttpGet]
[ValidateInput(false)]
public ActionResult SimpleSearch()
{
// I want to run this code of every ActionResult for a GET AND POST
if (!SessionStaticClass.IsUserLoggedIn())
{
return RedirectToAction("Login, Login");
}
}
Thanks
You can use an action filter:
public class NotLoggedInFilter : FilterAttribute, IResultFilter
{
public void OnResultExecuting(ResultExecutingContext filterContext)
{
if (!SessionStaticClass.IsUserLoggedIn())
{
filterContext.Result = new RedirectToAction("Login, Login");
}
}
}
You can then decorate controllers or actions with the attribute, or even have it run for all actions by adding it to the global filter collection.
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new NotLoggedInFilter());
}
However, you might want to have a look at authentication filters as a way of handling user authentication instead of using the SessionStaticClass class. A good overview of filter types in MVC can be found here.
I have seen how to do this globally in numerous posts and have it working in my code. The problem is that it's firing on EVERY call which isn't what I want, I only want it to fire on the calls to the methods where I have decorated the method with the attribute:
public class MyController : ApiController
{
[MyAttribute]
public void MethodA()
{
// Do Work - should have called the attribute filter
}
public void MethodB()
{
// Do Work - should NOT have called the attribute filter
}
}
This seems really basic to me and that I'm missing something but the only way I can get the attribute to fire at all is by registering it in global.asax using GlobalConfiguration.Configuration.Filters.Add(new MyAttribute()); which causes it to fire on requests to both MethodA and MethodB. Is there any way to register the attribute and only fire on the methods where it is tagged? I have tried using AttributeUsage to no avail.
EDIT Added code for attribute per comment although had to remove the inner workings. It's firing on all requests...
[AttributeUsage(AttributeTargets.Method)]
public class MyAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
// Do work
}
}
Edit 11/25
In addition to the information below where I accepted the answer I would like to add that a previous developer had removed the default ActionDescriptorFilterProvider with the following code that needed to be commented out in order for the default behavior of the custom action filters to take effect:
var providers = GlobalConfiguration.Configuration.Services.GetFilterProviders();
var defaultprovider = providers.First(i => i is ActionDescriptorFilterProvider);
// This line was causing the problem.
GlobalConfiguration.Configuration.Services.Remove(typeof(System.Web.Http.Filters.IFilterProvider), defaultprovider);
see HttpConfiguration.Filters Property - it clearly says
Gets the list of filters that apply to all requests served using this
HttpConfiguration instance.
but you need ActionFilter - which is, by definition,
Action filters contain logic that is executed before and after a
controller action executes. You can use an action filter, for
instance, to modify the view data that a controller action returns.
so basically what you need to do is to remove
GlobalConfiguration.Configuration.Filters.Add(new MyAttribute());
line from your Global.asax.cs file.
Using filter on the Action or Controller level you need to register it in the same ConfigureServices method but as a service:
services.AddScoped<ActionFilterExample>();
services.AddScoped<ControllerFilterExample>();
Finally, to use a filter registered on the Action or Controller level, you need to place it on top of the Controller or Action as a ServiceType:
namespace AspNetCore.Controllers
{
[ServiceFilter(typeof(ControllerFilterExample))]
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
[HttpGet]
[ServiceFilter(typeof(ActionFilterExample))]
public IEnumerable<string> Get()
{
return new string[] { "example", "data" };
}
}
}
I'm building a controller that other controllers can inherit (provide base functionality across site without repeating code):
public abstract class ApplicationController : Controller
{
protected ApplicationController()
{
//site logic goes here
//what is the value of agentID from the Action below??
}
}
public class AgentController : ApplicationController
{
public ActionResult Index(string agentID)
{
return View();
}
}
The logic that applies to the entire site will go into the constructor of the ApplicationController class.
The problem is in that constructor I need to access the value in the parameter from the Action, in this case agentID (it will be the same across the entire site). Is there a way to read that value in?
Actions happen after constructors. The value doesn't exist (hasn't been bound) in a constructor. The route data might be known in the constructor, but the action data binding certainly will not have happened yet. You can't get this value with certainty until the action has been invoked.
Route data can be accessed inside the controller via:
ControllerContext.RouteData.Values
However, it is incorrect to suppose that agentID can only be bound to route data. In fact, it could come from a server variable, a form field, a query string parameter, etc. My advice would be to pass it explicitly wherever it is needed. If nothing else, it makes your unit tests better.
I figured out how to do it ... very similar to Craig Stuntz's answer, but the difference is in how you reach the RouteData.
Using ControllerContext.RouteData.Values does not work in a regular method used this way (it does from the original controller, but not from a base one like I built), but I did get to the RouteData by overriding the OnActionExecuting method:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
string agentID = filterContext.RouteData.Values["agentID"].ToString();
OtherMethodCall(agentID);
}
There is, though you need to override the ControllerFactory. In your RegisterRoutes method in global.asax.cs, add this line:
public static void RegisterRoutes(RouteCollection routes) {
// Route code goes here
ControllerBuilder.Current.SetControllerFactory(typeof(MyControllerFactory));
}
And then define your MyControllerFactory class
public class MyControllerFactory : DefaultControllerFactory {
public override IController CreateController(RequestContext requestContext, string controllerName) {
// poke around the requestContext object here
return base.CreateController(requestContext, controllerName);
}
}
The requestContext object has all of the route data and values in it. You can use this to pass whatever you would like to the constructor of your controller.
Edit to add that this is how most of the popular Dependency Injectors (for example, StructureMap) work too.