I have simple controller:
public class TestController : Controller
{
public ActionResult Test(string r)
{
return View();
}
}
I have simple View Test.cshtml:
<h2>#ViewContext.RouteData.Values["r"]</h2>
#using (Html.BeginForm("Test", "Test"))
{
<input type="text" name="r" />
<button>Submit</button>
}
I have route rule in Global.asax:
routes.MapRoute(
null,
"Test/{r}",
new { action = "Test", controller = "Test",
r = UrlParameter.Optional }
);
I want to make such thing: user types route value in input, press submit and controller redirects him to page Test/value. But controller show just page with name Test everytime. ViewContext.RouteData.Values["r"] is empty too. I check in debug, Test action recieves user value of r correctly.
How can I realize my idea?
Thanks.
I'm super late to the party, but just wanted to post a solution for reference. Let's assume that this form has more than just a strong as it's input. Assuming there are other inputs, we can wrap up the inputs of the form into a class in our model, called TestModel whose properties maps to the id's of the form's inputs.
In our post, we redirect to the get, passing in the route values we need in the URL. Any other data can then be shuttled to the get using a TempData.
public class TestController : Controller
{
[HttpGet]
public ActionResult Test(string r)
{
TestModel model = TempData["TestModel"] as TestModel;
return View(model);
}
[HttpPost]
public ActionResult Test(string r,TestModel model) //some strongly typed class to contain form inputs
{
TempData["TestModel"] = model; //pass any other form inputs to the other action
return RedirectToAction("Test", new{r = r}); //preserve route value
}
}
You cannot do this without javascript. There are two types of methods that exist when submitting a <form>: GET and POST. When you use POST (which is the default), the form is POSTed to the url but all data entered in input fields is part of the POST body, so it is not part of the url. When you use GET, the input fields data is part of the query string but of the form /Test?r=somevalue.
I wouldn't recommend you trying to send user input as part of the path but if you decide to go that route you could subscribe to the submit event of the form and rewrite the url:
$('form').submit(function() {
var data = $('input[name="r"]', this).val();
window.location.href = this.action + '/' + encodeURIComponent(data);
return false;
});
As far as you are saying to post the form to Html.BeginForm("Test", "Test") you will be always posted back to the same page.
A solution could be to use an explicit Redirect to the action using 'RedirectToAction' (in view) or you can use javascript to change the form's action:
<input type="text" name="r" onchange="this.parent.action = '\/Test\/'+this.value"/>
Related
I'm trying to wrap my head around MVC.NET 3.
I use a _Layout.cshtml as base (structure, navigation). As part of the layout I want to display two links used for changing language/localization.
These should be displayed and clickable no matter what page is viewed, and after changing the localization I want to reload the view that called the action. So the page that the customer is looking at will be reloaded, with new localization set.
One way is to copy and paste the localization-changing action in each of the sites controllers, but is there no easier and more elegant way?
I tried creating a specific controller that handles the localization changing, but can't figure out how to return the viewer to the previous controller.
Perhaps this is easier accomplished with jquery?
This is the DIV from the _Layout file with the language changing buttons. It calls the action in the current controller, which means I have to define it in each of the site's controllers. (The good thing is the view that is returned is always correct.)
<div id="top>
#using (Html.BeginForm())
{
<button id="sv_flag" name="localization" title="#Resources.Global.Sv_Flag_Hover" value="sv-SE" />
<button id="en_flag" name="localization" title="#Resources.Global.En_Flag_Hover" value="en-GB" />
}
</div>
I also tried using a specific controller for this, but cannot think of how I could return to the current view afterwards? Like so:
#using (Html.BeginForm("LocalizationAction", "LocalizationController"))
...
Edit
Now using the suggestion from Darin, I send in the controller and action values from the layout page:
#using (Html.BeginForm("SetLocalization", "Localization",
new { returnController = #ViewContext.Controller.ValueProvider.GetValue("controller").RawValue,
returnAction = #ViewContext.Controller.ValueProvider.GetValue("action").RawValue }))
...
But I cannot get the localization changes to work, my controller action looks like this:
public ActionResult SetLocalization(string localization, string returnController, string returnAction)
{
Thread.CurrentThread.CurrentCulture = Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(localization);
return RedirectToAction(returnAction, returnController);
}
You could pass a returnUrl:
#using (Html.BeginForm("LocalizationAction", "LocalizationController", new { returnUrl = Request.Url.AbsoluteUri }))
{
...
}
and inside your LocalizationAction redirect to this url:
public ActionResult LocalizationAction(string returnUrl)
{
... do your localization stuff and once you are done get back:
return Redirect(returnUrl);
}
obviously you could do a little checking before blindly redirecting. Things like whether the returnUrl parameter is not empty and whether it belongs to your domain. You may take a look at how the default AccountController does that once it authenticates a user.
return Redirect(Request.UrlReferrer.ToString());
public ActionResult Change(String LanguageAbbrevation)
{
if (LanguageAbbrevation != null)
{
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(LanguageAbbrevation);
Thread.CurrentThread.CurrentUICulture = new CultureInfo(LanguageAbbrevation);
}
HttpCookie cookie = new HttpCookie("Language");
cookie.Value = LanguageAbbrevation;
Response.Cookies.Add(cookie);
return Redirect(Request.UrlReferrer.ToString());
}
I found a completely different (and much easier and elegant) solution to my problem.
I simply created a BaseController, that holds the action for changing the localization.
Then all controllers I add to the site inherit from this BaseController. This gives a single location for the code and does not require sending any return parameters, etc.
BaseController:
public class BaseController : Controller
{
[HttpPost]
public ActionResult Index(string localization)
{
Thread.CurrentThread.CurrentCulture = Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(localization);
return View();
}
}
Each of the site's Controllers then only need to inherit from it, and then mind their own actions:
public class ApplicationsController : BaseController
{
//
// GET: /Applications/
public ActionResult Index()
{
return View();
}
}
...
I have a controller with an Index method that has several optional parameters for filtering results that are returned to the view.
public ActionResult Index(string searchString, string location, string status) {
...
product = repository.GetProducts(string searchString, string location, string status);
return View(product);
}
I would like to implement the PRG Pattern like below but I'm not sure how to go about it.
[HttpPost]
public ActionResult Index(ViewModel model) {
...
if (ModelState.IsValid) {
product = repository.GetProducts(model);
return RedirectToAction(); // Not sure how to handle the redirect
}
return View(model);
}
My understanding is that you should not use this pattern if:
You do not need to use this pattern unless you have actually stored some data (I'm not)
You would not use this pattern to avoid the "Are you sure you want to resubmit" message from IE when refreshing the page (guilty)
Should I be trying to use this pattern? If so, how would I go about this?
Thanks!
PRG Stands for Post-Redirect-Get. that means when you post some data to the server back, you should redirect to a GET Action.
Why do we need to do this ?
Imagine you have Form where you enter the customer registration information and clicking on submit where it posts to an HttpPost action method. You are reading the data from the Form and Saving it to a database and you are not doing the redirect. Instead you are staying on the same page. Now if you refresh your browser ( just press F5 button) The browser will again do a similar form posting and your HttpPost Action method will again do the same thing. ie; It will save the same form data again. This is a problem. To avoid this problem, We use PRG pattern.
In PRG, You click on submit and The HttpPost Action method will save your data (or whatever it has to do) and Then do a Redirect to a Get Request. So the browser will send a Get Request to that Action
RedirectToAction method returns an HTTP 302 response to the browser, which causes the browser to make a GET request to the specified action.
[HttpPost]
public ActionResult SaveCustemer(string name,string age)
{
//Save the customer here
return RedirectToAction("CustomerList");
}
The above code will save data and the redirect to the Customer List action method. So your browser url will be now http://yourdomain/yourcontroller/CustomerList. Now if you refresh the browser. IT will not save the duplicate data. it will simply load the CustomerList page.
In your search Action method, You dont need to do a Redirect to a Get Action. You have the search results in the products variable. Just Pass that to the required view to show the results. You dont need to worry about duplicate form posting . So you are good with that.
[HttpPost]
public ActionResult Index(ViewModel model) {
if (ModelState.IsValid) {
var products = repository.GetProducts(model);
return View(products)
}
return View(model);
}
A redirect is just an ActionResult that is another action. So if you had an action called SearchResults you would simply say
return RedirectToAction("SearchResults");
If the action is in another controller...
return RedirectToAction("SearchResults", "ControllerName");
With parameter...
return RedirectToAction("SearchResults", "ControllerName", new { parameterName = model.PropertyName });
Update
It occurred to me that you might also want the option to send a complex object to the next action, in which case you have limited options, TempData is the preferred method
Using your method
[HttpPost]
public ActionResult Index(ViewModel model) {
...
if (ModelState.IsValid) {
product = repository.GetProducts(model);
TempData["Product"] = product;
return RedirectToAction("NextAction");
}
return View(model);
}
public ActionResult NextAction() {
var model = new Product();
if(TempData["Product"] != null)
model = (Product)TempData["Product"];
Return View(model);
}
When Urls are autogenerated using the Url.Action helper, if a page contains a line similar to
#Url.Action("Edit","Student")
is expected to generate a url like domain/student/edit and its working as expected.
But if the requested url contains some parameters, like domain/student/edit/210, the above code uses these parameters from the previous request and generates something similar even though I've not provided any such parameter to the Action method.
In short, if the requested url contains any parameters, any auto generated links of the page (served for that request) will include those parameters as well no matter if I specify them or not in the Url.Action method.
What's going wrong?
Use Darin's answer from this similar question.
#Url.Action("Edit","Student", new { ID = "" })
Weird, can't seem to reproduce the problem:
public class HomeController : Controller
{
public ActionResult Index(string id)
{
return View();
}
public ActionResult About(string id)
{
return View();
}
}
and inside Index.cshtml:
#Url.Action("About", "Home")
Now when I request /home/index/123 the url helper generates /home/about as expected. No ghost parameters. So how does your scenario differs?
UPDATE:
Now that you have clarified your scenario it seems that you have the following:
public class HomeController : Controller
{
public ActionResult Index(string id)
{
return View();
}
}
and inside Index.cshtml you are trying to use:
#Url.Action("Index", "Home")
If you request /home/index/123 this generates /home/index/123 instead of the expected /home/index (or simply / taken into account default values).
This behavior is by design. If you want to change it you will have to write your own helper which ignores the current route data. Here's how it might look:
#UrlHelper.GenerateUrl(
"Default",
"index",
"home",
null,
Url.RouteCollection,
// That's the important part and it is where we kill the current RouteData
new RequestContext(Html.ViewContext.HttpContext, new RouteData()),
false
)
This will generate the proper url you were expecting. Of course this is ugly. I would recommend you encapsulating it into a reusable helper.
Use ActionLink overload that uses parameters and supply null
You could register custom route for this action for example:
routes.MapRoute("Domain_EditStudentDefault",
"student/edit",
new {
controller = MVC.Student.Name,
action = MVC.Student.ActionNames.Edit,
ID = UrlParameter.Optional
},
new object(),
new[] { "MySolution.Web.Controllers" }
);
you then could use url.RouteUrl("Domain_EditStudentDefault") url RouteUrl helper override with only routeName parameter which generates url without parameters.
I have this code and I can't understand why it works this way
I have a model and view which is arbitrary and a very simple (but weird) controller
Here is my controller:
public partial class RouteController : Controller
{
[HttpGet]
public virtual ActionResult Create()
{
Create create = new Create();
return View("Create", create);
}
[HttpPost]
public virtual ActionResult Create(Create route)
{
return Create();
}
}
The first create method loads the view as normal. When the view posts back it runs the 2nd action which runs the first (as expected). The wierd part is the view is (re-)loaded with my previously entered data with errors (if any). I dont understand this because my model is empty. I was expecting it to post back with the same form as if it was loaded for the first time but with errors possibly.
Please explain.
That's the normal behavior of HTML helpers and it is by design. They first look at values contained in the ModelState and after that in the actual model. If you intend to modify some values on the model in a POST action you need to remove them from modelstate first:
For example:
[HttpPost]
public virtual ActionResult Create(Create route)
{
ModelState.Remove("SomeProperty");
route.SomeProperty = "some new value";
return View(route);
}
If you intend to completely modify everything as in your example you could clear the modelstate entirely:
[HttpPost]
public virtual ActionResult Create(Create route)
{
ModelState.Clear();
return Create();
}
Another possibility is to write your own TextBoxFor, HiddenFor, CheckBoxFor, ... helpers that will use the value in the model and not the one in the model state. Or yet another (non-recommended) possibility:
<input type="text" name="SomeProperty" value="#Model.SomeProperty" />
Of course in this case client validation among other things provided by the standard helpers won't work.
I'm trying to call an action in a separate controller with an actionlink.
The problem is that both the [HttpGet] and the [HttpPost] action gets called, and since the post-method returns the view from which the action is called, nothing gets displayed.
Get method:
[HttpGet]
public ActionResult View(int id, int index)
{
var form = formService.GetForm(id);
var pageModel = new PageViewModel();
var page = form.Pages.ElementAt(index);
ModelCopier.CopyModel(page, pageModel);
ModelCopier.CopyModel(form, pageModel);
return View(pageModel);
}
Post Method
[HttpPost]
public ActionResult View(PageViewModel pageViewModel)
{
if (!ModelState.IsValid)
{
return RedirectToAction("Details", "Forms", new { id = pageViewModel.FormId });
}
var pageToEdit = pageService.GetPage(pageViewModel.PageId);
ModelCopier.CopyModel(pageViewModel, pageToEdit);
pageService.SavePage();
return RedirectToAction("Details", "Forms", new {id = pageViewModel.FormId});
}
How it's called from the View (from a view returned by another controller):
#Html.ActionLink("View", "View", "Pages", new { id = Model.FormId, index = item.Index-1 }, null)
What am I doing wrong here? I essentially want it to work as an update/edit function. And the view returned contains a simple form for the viewmodel.
An action link issues a GET request. You will have to implement some JavaScript function that captures the url and parameters, and dynamically creates and submits a new form with a POST method, or does an Ajax POST. You could write your own HTML Helper, to wrap this functionality, but the default functionality of clicking an <a> tag (which is what is generated by a Html.ActionLink) will issue a GET request.
I don't think you'll be able to use an action link - see this question
The options are to use jQuery to post data or swap to a simple form and submit