I think I misunderstand something about MVC. I'm trying to do the following:
public class ControllerA : Controller
{
public ActionResult Index()
{
// do code
// perform action on ControllerB - something like:
// RedirectToAction("Action", "ControllerB");
// CARRY ON with more code
}
}
public class ControllerB : Controller
{
public void Action()
{
// do code
}
}
Obviously RedirectToAction("Action", "ControllerB"); isn't working. So how do I do it? I guess I could have all controllers that need to use Action() inherit from ControllerB but that feels a really bad way to do it. Please help!
You have to return the ActionResult from RedirectToAction()
return RedirectToAction("Action", "ControllerB");
is what you need to do if you want RedirectToAction to actually redirect to an action. After you clarified what "isn't working" means to you I think you should just have all controllers inherit from a base. That is a pretty standard approach.
public class ControllerA : ControllerB
{
public ActionResult Index()
{
// do code
Action();
// CARRY ON with more code
}
}
public class ControllerB : Controller
{
public void Action()
{
// do code
}
}
I believe the controller you are currently executing is an instance of the class so you would need to make an instance of controller B to be able to execute anything on it, so what you are trying to do there just won't really work without a hack.
I think however there is 2 methods to better get the results i think you are after:
1) Make a 'ControllerBase' class and have all controllers inherit from it instead of from 'Controller' then any shared code you can add into the base class (as a static method perhaps) and then all controllers can access it as a member nice and easy.
2) As MVC will make a straight up DLL you can add in new classes as you need, eg add a new project folder like 'Globals' add a new class file called 'Funcs' and there you have a static lib that you can access from anywhere, Funcs.SendEmail(); etc
If i'm off the mark ok! happy coding anyway heh
I have injected controllers with a factory method from the controller factory as a delegate (Func CreateController), and used that to create sub-controllers, such as in this circumstance. There's plenty of ways to accomplish your goal, but I think that might be quick way to get what you want working.
Related
Inside an asp.net mvc view you can access the model that is returned with the view.
If you are not in the view itself but another method that is run after the controller method completes, what [static?] method is there to get the current model of the current view being rendered?
I can access all the standard objects: session, request, response, etc.
I worked around by loading a Session variable but I don't like using session if the framework already holds it.
You can create an action filter which will be executed in the MVC request pipeline. If you want to execute some code after the action method's execution, you may override the OnActionExecuted method.
public class MyCustom : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var viewModel = filterContext.Controller.ViewData.Model;
var response = filterContext.HttpContext.Response;
var viewBag = filterContext.Controller.ViewBag;
// Use any of these as needed
base.OnActionExecuted(filterContext);
}
}
If you want this for all the requests, You can register this filter to the GlobalFilters collection inside the RegisterRoutes method.
GlobalFilters.Filters.Add(new MyCustom());
In ASP MVC 4, is it possible from a view to call another controller action?
I'm on http://localhost:57456/Archers and I would like to call a method from the controller Participe (So should be http://localhost:xxxxx/Participe/Action).
This is only applying to this action, so I don't want to redirect every action to this controller.
You can use Html.Action inside your view to call child actions
<div> #Html.Action("Action","Participate") </div>
I would suggest you call your ParticipeController from the first action, and include the controller result into the view Data or the model to return to the view:
public class ArchersController {
public ActionResult Index() {
// your current code here
// your custom call
var result = new ParticipeController().Action("your params here");
ViewData["ParticipeResult"] = result;
// return View();
}
}
Maybe though, some responsibility principles could be applied here in order to isolate the Participe call you want to make here into its own class or method.
Say the requirement is to redirect to another page after successfully saving a model in an ASP.NET MVC controller:
[HttpPost]
public ActionResult Index(ViewModel viewModel)
{
if (ModelState.IsValid)
{
// Do save;
return RedirectToAction("whatever"); // <--- here's the problem
}
// display validation errors and so
return View(viewModel);
}
This would work fine unless the controller was rendered as a Child Action:
#{
Layout = "my layout";
Html.RenderAction("something else, a view for example");
Html.RenderAction("Index action of the above controller"); // <----
}
In this case RedirectResult class would check and see that the context is a child action and would throw the exception: "Child actions are not allowed to perform redirect actions".
I understand that writing to Response stream is already in progress and a redirect cannot take place here but nevertheless one has to be able to redirect to other pages after a server-side action in a controller even if that action is a child action. We use Layouts and RenderActions to reuse the shared parts of the page design.
How would you implement such a redirect in such a controller?
Edit:
The main goal is to reuse View/Controllers that do a specific job and split them to logical concepts like displaying some data or providing an edit form. My approach here is to use RenderAction to render their result into a container. Container view (main page's Index action) acts as a traditional asp.net Page, its Layout view as a Master page and edit and view controller/views are equivalent to User Controls (modules). The problem is there is no way to Redirect the Response after something has been written to it:
I'll submit a new answer in an attempt to keep things clean.
A normal mvc flow goes like this :
Http command reaches 1 controller which acts as a choirmaster(aka controller) and calls several logic containers(e.g services / commandHandlers) :
public ActionResult Index(){
var data = _yourService.FetchData();
return View(data);
}
This controller renders 1 view which can have multiple partials
#{
Layout = "my layout";
}
<p>Some html</p>
Html.RenderPartial("A shared partial");
Html.RenderPartial("shared\yourUserControl", Model.PropertyOrSomething);
If the partial contains too much logic to generate you could add a RenderAction or create an htmlHelper extension.
But neither of these should be able to control the flow of your request, a save or something that can redirect should in my opinion never be called from inside a view.
I assume you want to reuse the code in your controller so badly because it is getting quite big.
My advice would be to try and clear that controller up as much as possible by delegating as much of the logic upwards as you can.
Just by looking at your controller method for 5 seconds you should get an idea of what this action will do, if that's not the case : refactor it ! :)
If i understand correctly you have a parent controller which accepts a saveAction and while rendering the view you call another(?) saveAction as a child.
This flow feels unnatural to me. The parent action and only the parent action should handle the save command. You can render as many child actions as you want as long as they are only used to render some html(as this is what the view does). Don't let them handle redirection or saving, that way of working is everything but transparant for colleges or future you.
The controller controls the flow, not the view.
Edit:
A normal setup would be having 2 actions, eg: Index and Put.
public ActionResult Index(){
//fill model with dropdown data etc
return View();
}
public ActionResult Put(viewModel data){
if (ModelState.IsValid)
{
// Do save;
return RedirectToAction("whatever"); // <--- here's the problem
}
// display validation errors and so
return View("Index",viewModel);
}
Edit2:
If you return View("Index",viewModel) you will generate your index view with it's layout and the validation messages will be located in the modelstate.
Your view however should only have 1 childaction(or more if there are multiple, as long as it's not the save action).
Your Index view could look like this :
#{
Layout = "my layout";
}
Html.RenderAction("something else, a view for example");
#Html.BeginForm("Put","YourController"){
//all your input controls which will also show the validation errors
}
Edit 3:
If you want to reuse html code you should use #Html.Partial or Html helper extension methods. Note that if you pass no model the parents model is passed but you can pass a submodel to match the type safety of the partial.
It would look something like this :
#{
Layout = "my layout";
}
Html.RenderAction("something else, a view for example");
Html.RenderPartial("shared\yourUserControl", Model.PropertyOrSomething);
Try using AJAX in your view to do this, and instead of returning a RedirectToAction in your controller, return a JSON object:
View:
$.ajax({
url: '<%: Html.ResolveUrl("~/ControllerFolder/ControllerName/") %>',
type: "POST",
data: data,
success: function (result) {
$("#Div").html(result);
if (result.redirectUrl != null)
{
window.location = result.redirectUrl;
}
}
});
Controller:
[HttpPost]
public ActionResult Index(ViewModel viewModel)
{
if (ModelState.IsValid)
{
// Do save;
return Json(new { redirectUrl = Url.Action("NewAction", "NewController", RouteValues)});
}
// display validation errors and so
return View(viewModel);
}
Hope this helps...
It's really easy to return a different View from the Controller:
return View("../Home/Info");
However, I need a model in the Info view. I have a lot of stuff going on in the Info() action result method. I can just copy it and have something like this:
var infoModel = new InfoModel {
// ... a lot of copied code here
}
return View("../Home/Info", infoModel);
But that is not reasonable.
Of course I can just redirect:
return RedirecToAction("Info");
But this way the URL will change. I don't want to change the URL. That's very important.
You can call right to another action from within an action, like this:
public ActionResult MyAction(){
if(somethingOrAnother){
return MyOtherAction();
}
return View();
}
//"WhichEverViewYouNeed" is required here since you are returning this view from another action
//if you don't specify it, it would return the original action's view
public ActionResult MyOtherAction(){
return View("WhichEverViewYouNeed", new InfoModel{...});
}
It looks like you want to invoke an action from a different controller. I'd suggest that you might want to simply render a view that renders that action using Html.Action() instead of trying to tie the two together in the controller. If that's unreasonable then you might want to create a base controller that both controllers can derive from and put the shared code to generate the model in base controller. Reuse the view as needed.
public ActionResult Foo()
{
return View();
}
Foo View
#Html.Action( "info", "home" )
Why not just invoke the method of the action?
I have a problem with my data binding from View to Controller
I have an object that includes 3 other objects that I want to pass from controller to view
ModelView { Product, PagingInfo, Filter }
So this is how it looks like from controller
public ViewResult List(ModelView mv, int page = 1) {
var viewModel = new ModelView() { ... }
return View(viewModel);
and the View looks like this
Inherit = "viewModel"
using(html.BeginForm()) { Html.EditorFor(x => x.Filter.Name) ... }
Questions are:
Am i right for binding ModelView as a parameter in the controller? or should I bind Filter instead?
When I used a debugger, it seemed like whatever I put in textfield (Html.editorfor) doesn't get binded back the controller
Please Help
Thanks
My bad! It was working the entire time, I totally forgot that I put the default value as null in the routing system.
Sorry for the trouble :(