I've been seeing some unexpected behavour in my MVC application.
Lets say I have 3 action methods
Details
Details_Fr
Details_En
The idea behind the 2nd and 3rd is that they switch the language and then redirect to the "real" Details action.
However, when I call RedirectToAction with a breakpoint in "Details" it is not reached. This is the case when I visit the pages in this order "Controller/Details" and from there "Controller/Details_Fr".
Here are my actions:
public ActionResult Details()
{
return View(new MyViewModel());
}
public ActionResult Details_Fr()
{
this.SetLanguage(CultureInfo.GetCultureInfo("fr-CA"));
return RedirectToAction("Details");
}
public ActionResult Details_En()
{
this.SetLanguage(CultureInfo.GetCultureInfo("en-US"));
return RedirectToAction("Details");
}
I'm not looking for a solution as that's easily done by changing RedirectToAction to View(new MyViewModel()). I am looking for an explaination so I understand what and why this is happening.
Thanks!
You should be setting the language (CurrentCulture and CurrentUICulture on CurrentThread) in ActionFilter attribute instead of creating those horrible _Fr and _En actions....!
Related
I'm new to MVC and trying to create a wizard-style series of views, passing the same model instance from one view to the next, where the user completes a little more information on each form. The controller looks something like this:-
[HttpGet]
public ActionResult Step1()
{
return View();
}
[HttpPost]
public ActionResult Step1(MyModel model)
{
if (!ModelState.IsValid)
return View(model);
return View("Step2", model);
}
[HttpPost]
public ActionResult Step2(MyModel model)
{
if (!ModelState.IsValid)
return View(model);
return View("Step3", model);
}
// etc..
Questions:-
When I submit the form from the Step1 view, it calls the Step1 POST method and results in the Step2 view being displayed in the browser. When I submit the form on this view, it calls the Step1 POST method again! I got it to work by specifying the action and controller name in Html.BeginForm(), so I'm guessing that the parameterless overload just POSTs back to the action that rendered the view?
I've noticed that the browser's address bar is out of sync with the current view - when I'm on the Step2 view it still shows the Step1 URL, and when on Step3 it shows the Step2 URL. What's going on?
Another approach I've seen for passing a model between views is to put the model in TempData then use RedirectToAction(). What are the pros and cons of this method versus what I'm currently doing?
I won't be providing any "back" buttons of my own in the wizard. Are there any pitfalls to be aware of regarding the browser's back button, and do either of the above two approaches help (or hinder)?
Edit
Prompted by #StephenMuecke's comment I've now rewritten this to use a single view. I tried this once before but had difficulties round-tripping a "step number" to keep track of where I was in the wizard. I was originally using a hidden field created with #Html.HiddenFor', but this wasn't updating as the underlying model property changed. This appears to be "by design", and the workaround is to create the hidden field using vanilla HTML (
Anyway the one-view wizard is now working. The only problem is the old chestnut of the user being able to click the back button after they have completed the wizard, make a change, and resubmit a second time (resulting in a second DB record).
I've tried adding [OutputCache(NoStore = true, Duration = 0, VaryByParam = "None")] to my POST method, but all this does is display (in my case) a Chrome error page suggesting that the user clicks refresh to resubmit the form. This isn't user friendly and doesn't prevent a second submit.
you can use RedirectToAction() in this case without worrying about TempData. Just add your model as a parameter to each action and use RedirectToAction("Step2", model);
[HttpGet]
public ActionResult Step1()
{
return View();
}
[HttpPost]
public ActionResult Step1(MyModel model)
{
if (!ModelState.IsValid)
return View(model);
return RedirectToAction("Step2", model);
}
[HttpGet]
public ActionResult Step2(MyModel model)
{
return View(model);
}
[HttpPost]
public ActionResult Step2(MyModel model)
{
if (!ModelState.IsValid)
return View(model);
return RedirectToAction("Step3", model);
}
// etc..
The answer to #1 is found in #2.. if you dont specify the Action in you Html.BeginForm() it posts to the current url.
Using TempData to avoid model displaying in url.
[HttpGet]
public ActionResult Step1()
{
return View();
}
[HttpPost]
public ActionResult Step1(MyModel model)
{
if (!ModelState.IsValid)
return View(model);
TempData["myModel"] = model;
return RedirectToAction("Step2");
}
[HttpGet]
public ActionResult Step2()
{
var model = TempData["myModel"] as MyModel;
return View(model);
}
[HttpPost]
public ActionResult Step2(MyModel model)
{
if (!ModelState.IsValid)
return View(model);
TempData["myModel"] = model;
return RedirectToAction("Step3");
}
// etc..
Another option would be to add the name of the next action to ViewBag and set your actionName in each BeginForm()
[HttpGet]
public ActionResult Step1()
{
ViewBag.NextStep = "Step1";
return View();
}
[HttpPost]
public ActionResult Step1(MyModel model)
{
if (!ModelState.IsValid)
{
ViewBag.NextStep = "Step1";
return View(model);
}
ViewBag.NextStep = "Step2";
return View("Step2", model);
}
[HttpPost]
public ActionResult Step2(MyModel model)
{
if (!ModelState.IsValid)
{
ViewBag.NextStep = "Step2";
return View(model);
}
ViewBag.NextStep = "Step3";
return View("Step3", model);
}
//View
#using (Html.BeginForm((string)ViewBag.NextStep, "ControllerName"))
{
}
I'd prefer to add NextStep as a property to MyModel and using that instead of using ViewBag though.
I understand the thought behind your approach and don't have any issues with it. Unfortunately, I don't believe that ASP.NET MVC is geared very well for passing the the same view model (with data!) between different actions.
Typically, the scaffolded actions in the controller will either create a model item or find it by identifier in the database.
I don't know if this would help, but you could try to save it to the database on every step, and then retrieve it by identifier, or you could also save it to a session and grab it that way.
One issue I do see with your approach is you have Step2 set as a get, yet you probably want to post data to it from Step1 instead of using a query string. You may need to reconcile that issue.
I can change the method name but can anyone tell me why this doesn't work? How can i make it working without changing method name. Also I don't want to specify the action name in the view. Is it possible?
[HttpGet]
[Route("AddUpdateCategories/{storeId}")]
public ActionResult AddUpdateStoreCategories(int storeId)
{
return View();
}
[HttpPost]
public ActionResult AddUpdateStoreCategories(int StoreId,int[] ShopCategoryId)
{
return null;
}
Problem is post action is not getting called on submit.
You don't have to change the method name. The problem is that this post action has no route. If you use attribute routing, you have to specify a route for each action. Both of these actions would end up with the same route attribute, but the [HttpPost] attribute is enough for the routing framework to work out which one to use.
[Route("AddUpdateCategories/{storeId}")]
public ActionResult AddUpdateStoreCategories(int storeId)
{
return View();
}
[HttpPost]
[Route("AddUpdateCategories/{storeId}")]
public ActionResult AddUpdateStoreCategories(int StoreId,int[] ShopCategoryId)
{
return null;
}
Recently I've created on controller call DashboardVideos and an action method called Index.
And after Add Or Update, I'm redirecting it to Index page using
RedirectToAction("Index", "DashboardVideos").
but this code redirecting it to /DashboardVideos/ and it says
HTTP Error 403.14 - Forbidden
The Web server is configured to not list the contents of this directory.
so the issue is by default it's supposed to load Index page when I say /Dashboard
But its not, same url pattern working with all other controller (So I don't think there's anything wrong with routing pattern).
Any help would be appreciated.
Code:
public class DashboardVideosController : BaseController
{
private readonly IDashboardVideosComponent socialTagComponent;
public DashboardVideosController()
{
socialTagComponent = ComponentFactory.Get<IDashboardVideosComponent>();
}
// GET: DashboardVideos
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult AddUpdate(DashboardVideosModel socialTagChannel)
{
//Save data to database
return RedirectToAction("Index", "DashboardVideos");
}
}
Simply write this if both actions are in same controller.
public ActionResult AddUpdate(DashboardVideosModel socialTagChannel)
{
//Save data to database
return RedirectToAction("Index");
}
Try to take a look at your "RouteConfig" class and you can specify custom routes there. Also It is possible if the call comes from AJAX it can go directly to action without redirecting. Did you tried to Debug the code?
I have this mentally disturbing problem where I used the name View for my Views. As normal, created the initial page and the postback to save.
public ActionResult View(int id)
{
Models.PageContent model = Controllers.PageContent.Get(id);
return View(model);
}
[HttpPost]
[ValidateInput(false)]
public ActionResult View(Models.PageContent model)
{
if (ModelState.IsValid)
{
Controllers.PageContent.UpdatePageContent(model.PageContentID, model.Title, model.Text);
ViewBag.Success = true;
return RedirectToAction("Index");
}
else
{
return View(model);
}
}
What is weird is that both the normal view and the post back view get called and it just doesn't work.
Ah, okay, that makes sense then as to what is happening. In your View(int id) method you're calling View(model), where model is of type PageContent. When .NET tries to resolve overloaded methods it picks the most specific, which would be the method right below in the same controller. The method that is in the base control is actually defined as View(Object model) which is less specific than View(PageContent model), so it resolves the call to the HttpPost version of the method rather than the base class version of the method.
The comment about changing the name of the method is correct. Your choice of method name is conflicting with the framework-provided View method.
If you want to use View in the URL you can use the ActionName attribute.
[ActionName( "View" )]
public ActionResult GetView( int id )
[HttpPost]
[ActionName( "View" )]
public ActionResult PostView( Models.PageContent model )
I did a test at home on my VS2013 and I actually got a green squiggly to warn me. For some reason,on both mine and another developers this did not show. Possibly because it was MVC3 and not 4 like my test.
in Asp.Net MVC if I decorate an action method with attribute NonAction then it wont be allowed to be called by the user visiting the site.
same happens when I make it private
So whats the difference between the two and is there a special purpose for which NonAction attribute has been made?
For example whats the difference between
[NonAction]
public ActionResult SomeAction(){}
And
private ActionResult SomeAction(){}
in the context of asp.net MVC of course I know one is public and the other one is private
That's the only difference. The attribute is used when you want a method that has a signature that would make it an action, but that you don't want to be an action.
An example for a use for that is a method that action methods call to produce the ActionResult for them:
[NonAction]
public JsonResult JsonInfo(string id, string value) {
return Json(new { id = id, value = value });
}
public JsonResult GetBusInfo() {
return JsonInfo("4", "Bus");
}
public JsonResult GetCarInfo() {
return JsonInfo("8", "Car");
}
The reason to make it public instead of private would be so that actions in other controllers could also use it.
Both works same with action method,you can use them seperately or together.
[NonAction]
private ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
FEED_TBL fEED_TBL = db.FEED_TBL.Find(id);
if (fEED_TBL == null)
{
return HttpNotFound();
}
return View(fEED_TBL);
}
If declare it like the above code then when we will try to go to details action method it will not go to it.Rather it will show the error.
{{ HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.}}
This shows that our detail link on view does found any reference to details action method and our controller to.