Query String from BeginForm in MVC 3 - asp.net-mvc

I want to use BegingForm with Get method and this is what I do
#using (Html.BeginForm("Search","Home",FormMethod.Get))
{
//My input elements
}
public class HomeController : Controller
{
public ActionResult Search(string queryString)
{
}
}
but query string always comes back as null. I think, I need to do something with route but no luck
routes.MapRoute(
"SearchRoute", // Route name
"Home/Search{queryString}", // URL with parameters
new { controller = "Home", action = "Search", filter = UrlParameter.Optional } // Parameter defaults
);
Obviously, the coming url to the server is something like
Home/Search?query="blah"&query2="blah"&query3="blah"
What am I doing wrong? What is the correct way to get the query parameters in my controller when I want to use get with beginform?
Also, what if the content of my BeginForm can change and so the query string parameter names could be different depending on the rendered page but I want one Search method that analyze the query string and do the right thing?
Also, is a way for them to query parameters to come in a dictionary?

Obviously, the coming url to the server is something like
Home/Search?query="blah"&query2="blah"&query3="blah"
That's how HTML <form> with method GET works and this has nothing to do with ASP.NET MVC, it's plain HTML. You can't do much about it other than having your controller action look like this:
public ActionResult Search(SearchViewModel model)
{
...
}
Where SearchViewModel will contain properties for each input field on this form. Also you don't need this SearchRoute as it won't work that way.
This being said you could probably use javascript in order to subscribe for the onsubmit event of the form, cancel the default submission (which exhibits the behavior you are observing currently), manually fetch all the values inside your form and then manually generate the url you want and redirect to it using window.location.href = '....';. I am mentioning this only for completeness but absolutely not as something that I recommend or that you should ever do.

If you want to get the items from the query string, just use the "Request" object from the ControllerBase:
public ActionResult Search()
{
var queries = new List<string>();
foreach (var parameter in Request.QueryString)
{
queries.Add(parameter.ToString());
}
//Do Stuff with the query parameters...
return View("Index");
}
And "Request.QueryString" is a dictionary just as you wanted :)

Related

SurfaceController generate incorrect URL?

A Form is posted to a SurfaceController 'Submit' action. After saving to the database, it redirects to another action 'LeadContact', in the same controller (using RedirectToAction()), passing in 'Id' as a paramter. The model is then populated and passed to the 'LeadContact' view.
Not sure if I'm doing this correctly, but when 'LeadContact' renders in the browser, the URL shows as
http://localhost:50656/umbraco/Surface/HealthInsurance/LeadContact?leadOptionId=70`
while I'm expecting it to be
http://localhost:50656/HealthInsurance/LeadContact?leadOptionId=70
In short it adds /umbraco/SurfaceContact' into url.
Can you please advise how I can correct it and what I'm doing wrong ?
public ActionResult Submit(FormCollection form)
{
//Some logic and later redirect to another action 'LeadContact'
return RedirectToAction("LeadContact", new { leadOptionId = _id});
}
public ActionResult LeadContact(int leadOptionId)
{
MyViewModel model = new MyViewModel();
//Lines of code to populate data into model
return View("LeadContact", model);
}
Thanks for your help and sharing.
Check your project properties, under Web you most likely have a virtual path specified.

Is there a way to generate the post URL in the GET action if both the GET and the POST methods share the same name?

Is there a way to generate the post URL in the GET action if both the GET and the POST methods share the same name?
The reason for needing to generate the post URL in the controller is that we aren't dealing with the views but only providing the ViewModel that contains a payload in the form of JSON. And the front-end needs to know where to post form values to.
[HttpGet]
public ActionResult MethodName(string id)
{
...
}
[HttpPost]
public JsonResult MethodName(ViewModel model)
{
...
}
e.g. the get URL would be:
/controller/methodname/123
and the post URL would be:
/controller/methodname
in the get action, I need to feed the front-end with the post URL of which the form values would be consumed.
using Url.Action("methodname", "controller", new { id = string.Empty}) would generate a URL that's the same to the post URL, but it's sort of hack-ish way
Another option would be to define a route for the post method and use Url.RouteUrl("PostMethodName") to generate the URL.
Neither of these seem ideal, so is there another (cleaner) way to get this done?
Walter,
I am failing to understand how this is "hack-ish." You are providing a restricted post action with a strongly typed model, the only difference if your action result type. The URL's can be the same if you wish.
Alternativtly you can create just an action for your aJax method such as.
[HttpPost]
public JsonResult ajaxMethod(ViewModel model){
//...
}
This is restrictive to just the HttpPost method and contains your view model. Actions dont need views they simply return an action result. If you wanted to get very bare-bones and action can be an object returning anything. Your route for this action is similar to all other routes:
#Url.Action("ajaxMethod", "controller")
to generate
/controller/ajaxMethod
I may have completly missed the question, please let me know if this doesnt help.
EDIT: Updated from comments.
Just to add if you would like to generate a route or action url in your controller action you can simply use the Url helper in your action such as.
ViewBag.Url = Url.Action("MethodName", "Controller");
Another option is to create a custom Route implementation. A very basic one that includes a line to fix your problem (Drop the existing route parameters) can look like this:
public class CustomRoute : Route
{
public CustomRoute(string url, IRouteHandler routeHandler)
: base(url, routeHandler)
{
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var routeData = base.GetRouteData(httpContext);
if (routeData == null)
{
return null;
}
/* custom implementation here */
return routeData;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
/*
* GetVirtualPath is called until a suitable route is found.
* This means modifying the passed RouteValueDictionary has consequenses for subsequent calls.
* Therefore, always work with a copy if you want to modify any values: new RouteValueDictionary(values)
*/
/*
* Drop the existing route parameters. Implicitness can only cause confusion.
*/
requestContext = new RequestContext(requestContext.HttpContext, new RouteData());
return base.GetVirtualPath(requestContext, values);
}
}
You probably also want to have MapRoute methods for your custom route. For this, you just need to look at the original implementation, copy paste it and replace Route with CustomRoute.

Pass a big object to an action on a controller from a view

I want to pass a big object to a controller's action from a view. Like so:
View
<div>#Html.ActionLink("Send us an email", "Index",
"Email", new { o = #Model.Exception }, null)</div>
Controller
public class EmailController : Controller
{
[AllowAnonymous]
public ActionResult Index(object o)
{
new BaseServices.Emailer().SendEmail(o);
return View();
}
}
The thing is: the object being passed is so large that I guess that MVC is unable to make an argument out of that and add it to the route table/dictionary. So, my email controller's Index action is never called. The code bombs off somewhere in between.
No, you can't do this. ASP.NET MVC is not some magic. It relies on standard HTTP and HTML. And as you know in HTTP when you are using a GET request, there's no notion of .NET objects. You cannot ask how to pass an object in a web application because this is not defined.
There's a notion of query string parameters. So that's what you can pass => simple query string parameters:
#Html.ActionLink(
"Send us an email",
"Index",
"Email",
new { id = Model.Exception.Id, text = Model.Exception.Text },
null
)
Where the magic comes is that ASP.NET MVC will now use the 2 simple query string parameters (id and text) to map them to the corresponding properties of your view model inside the controller action.
But of course for this to work ASP.NET MVC needs to know the type of the model. You cannot just use object because this type doesn't have id nor text properties.
So:
public ActionResult Index(MyViewModel o)
Now but what about sending complex types? Well, the question that you have to ask to yourself is why on the first place this type was passed to the view? Was it because tfhe user was supposed to edit some of its properties? Is so then you should use an HTML form containing input fields allowing the user to edit them.
But since you have stuck this object into an anchor then what's the point? The server could fetch this object from wherever it fetched it in the first place. So all you need is to pass a simple id to the server:
#Html.ActionLink(
"Send us an email",
"Index",
"Email",
new { id = Model.Exception.Id },
null
)
and have the controller action take this id as parameter:
public ActionResult Index(int id)
Alright now you know the id => therefore you could retrieve the corresponding entity from wherever this entity is persisted.
Now some people might suggest you storing the object into the session before rendering the view and then retrieving this object from the session. Personally I am not a big fan of the session as it introduces state into the application. This means that you can never invoke the second action without first invoking the first action. This also means that you cannot bookmark the second action into the browser favorites. This also means that if you are running your application in a web-farm you can no longer store the session in-memory => you will have to use an out-of-process storage for this session. Sessions are way too much of a hassle.
You can't really pass it in the view.
Instead, consider storing the exception in TempData in the controller that renders the view....
public ActionResult DisplayErrorAndOptionToEmailIt()
{
TempData["LastError"] = m.Exception;
return View();
}
and then when the request comes in retrieve it from temp data and email it
public ActionResult SendTheEmail()
{
var e = TempData["LastError"] as Exception;
if (e != null)
{
EmailHelper.SendExceptionEmail(e);
}
return View();
}
On a side note, it's not the best practice to store complete objects. If possible, store only what you need:
public ActionResult DisplayErrorAndOptionToEmailIt()
{
TempData["LastError"] = m.Exception.Message;
return View();
}

How to route legacy QueryString parameters in ASP.Net MVC 3?

I am using a third party service that does an async callback to a URL I provide to them.
So I tell them to use http://www.mysite.com/Status/Incoming.
This must obviously map to an Incoming() method on my StatusController.
However, what I don't have control over is the format of the parameters they call my URL with.
E.g. They will do a callback such as: http://www.mysite.com/Status/Incoming?param1=val1&param2=val2&param3=val3
I want to map this to the parameters of my action method: Incoming(string param1, string param2, int param3)
How do I do this?
I have found a lot of stuff about custom routing, but nothing about legacy QueryString parameters.
There is no such thing as legacy query string parameters. There are query string parameters and they are part of the HTTP specification. And assuming that the http://www.mysite.com/Status/Incoming?param1=val1&param2=val2&param3=val3 url is called you don't need any route to make it map to the following action (the default route will do just fine):
public ActionResult Incoming(string param1, string param2, string param3)
{
...
}
The default model will take care of binding those values.
Why not use a catch all?
routes.MapRoute(
"Incoming",
"Status/Incoming/{*path}", // URL with parameters
new { controller = "Status", action = "Incoming"}
);
then in your controller,
public ActionResult Incoming(string path){
// the next line would probably be better off in a model binder, but this works:
var dictionary = path
.Substring(path.IndexOf("?")+1)
.Split("&")
.Select(x =>
{
var kvArray = x.Split("=");
return new KeyValuePair<string, string>(kvArray[0], kvArray[1]);
})
.ToDictionary(x=>x.Key,x=>x.Value);
return Incoming(dictionary);
}
public ActionResult Incoming(Dictionary<string,string> dictionary){
//do stuff
}
All that being said, I think using the Request.QueryString is probably a better approach. As long as you are using MVC, it is accessible from your controller. However, if you can guarantee that the correct parameters will be passed then Darin's approach is going to be the best choice.
When I've had to deal with this before now, I just use the "legacy" call of Request.QueryString. It still works, even if it isn't very graceful.

MVC Post with dynamic routing

I've created a routing structure whereas the action part of the URL serves as a dynamic handler for picking a specific user created system name. i.e.
http://mysite.com/Systems/[SystemName]/Configure, where [SystemName] designates the name of the system they would like to configure.
The method that routes the system is the following:
public ActionResult Index(string systemName, string systemAction)
{
ViewData["system"] = _repository.GetSystem(systemName);
if (systemAction != "")
{
return View(systemAction);
}
else
{
// No Id specified. Go to system selection.
return View("System");
}
}
The above method sets the system to configure and routes to a static method where the view is displayed and a form awaits values.
The question I have is that when I create my configuration view, I lose my posted values when the form is submitted because it routes back to the above Index controller. How can I determine if data is being posted when hitting my above Index controller so that I can make a decision?
Thanks!
George
Annotate the controller method that handles the POST like this:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(string systemName, string systemAction)
{
// Handle posted values.
}
You can have a different method in your controller that handles the GETs:
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Index(string systemName, string systemAction)
{
// No posted values here.
}
Note that, although I have copied the same method and parameters in each case, the signature for the second method (parameters and types) will have to be different, so that the two methods are not ambiguous.
The NerdDinner tutorial has examples of this.

Resources