MVC Post with dynamic routing - asp.net-mvc

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.

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.

MVC detect when model is empty

I'm new to MVC, so I'm trying to figure out some best practices.
Suppose I have a controller HomeController method Index(MyViewModel model):
public ActionResult Index(MyViewModel model)
{
//if loading the page for the first time, do nothing
//if the page has been posted data from somewhere, then I want to use
// some of the arguments in model to load other data, like say search results
}
When I navigate to the /Index page, I (myself) expect the model object to come through as null, but it doesn't. MVC (somehow) creates a MyViewModel for me.
My question is, what's the best way or most consistent to determine if model was created automatically, or via a post?
Ideas:
Create a property on MyViewModel that gets set when the view is posting back
Check for if the Request.HttpMethod == "GET" or "POST"
Something else?
You should use different actions for your GET and POST requests. Don't try and make a single method do too much.
[HttpGet]
public ActionResult Index()
{
// handle the GET request
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
if (ModelState.IsValid)
{
// it's a post and the data is valid
}
}
The correct method will then be called depending on whether it's a GET or POST
Create two actions, one which accepts a model instance and one which doesn't.
Even though you're "going to the same page" you are in fact performing two distinctly different actions. The first action loads an initial page, the second action posts some value to be acted upon. Two actions means two methods:
[HttpGet]
public ActionResult Index()
{
// perform any logic, but you probably just want to return the view
return View();
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
// respond to the model in some way
return View(model);
// or return something else? a redirect? it's up to you
}
Note that this kind of breaks your restful URLs. Consider semantically what you're doing in these actions:
Viewing an index
Posting to an index
The first one makes sense, but the second one probably doesn't. Normally when you POST something you're doing something related to a model or action of some sort. "Index" doesn't really describe an action. Are you "Create"-ing something? Are you "Edit"-ing something? Those sound like more meaningful action names for the POST action.

Query String from BeginForm in MVC 3

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 :)

Purpose of ActionName

What's the benefit of setting an alias for an action method using the "ActionName" attribute? I really don't see much benefit of it, in providing the user the option to call an action method with some other name. After specifying the alias, the user is able to call the action method only using the alias. But if that is required then why doesn't the user change the name of the action method rather then specifying an alias for it?
I would really appreciate if anyone can provide me an example of the use of "ActionName" in a scenario where it can provide great benefit or it is best to use.
It allows you to start your action with a number or include any character that .net does not allow in an identifier. - The most common reason is it allows you have two Actions with the same signature (see the GET/POST Delete actions of any scaffolded controller)
For example: you could allow dashes within your url action name http://example.com/products/create-product vs http://example.com/products/createproduct or http://example.com/products/create_product.
public class ProductsController {
[ActionName("create-product")]
public ActionResult CreateProduct() {
return View();
}
}
It is also useful if you have two Actions with the same signature that should have the same url.
A simple example:
public ActionResult SomeAction()
{
...
}
[ActionName("SomeAction")]
[HttpPost]
public ActionResult SomeActionPost()
{
...
}
I use it when the user downloads a report so that they can open their csv file directly into Excel easily.
[ActionName("GetCSV.csv")]
public ActionResult GetCSV(){
string csv = CreateCSV();
return new ContentResult() { Content = csv, ContentEncoding = System.Text.Encoding.UTF8, ContentType = "text/csv" };
}
Try this code:
public class ProductsController
{
[ActionName("create-product")]
public ActionResult CreateProduct()
{
return View("CreateProduct");
}
}
It is also helpful when you need to implement method overloading.
public ActionResult ActorView()
{
return View(actorsList);
}
[ActionName("ActorViewOverload")]
public ActionResult ActorView(int id)
{
return RedirectToAction("ActorView","Home");
}
`
Here one ActorView accepts no parameters and the other accepts int.
The first method used for viewing actor list and the other one is used for showing the same actor list after deleting an item with ID as 'id'.
You can use action name as 'ActorViewOverload' whereever you need method overloading.
This class represents an attribute that is used for the name of an action. It also allows developers to use a different action name than the method name.

ASP.NET MVC controller parameter optional (i.e Index(int? id))

I have the following scenario: my website displays articles (inputted by an admin. like a blog).
So to view an article, the user is referred to Home/Articles/{article ID}.
However, the user selects which article to view from within the Articles.aspx view itself, using a jsTree list.
So I what I need to do is to be able to differentiate between two cases: the user is accessing a specific article, or he is simply trying to access the "main" articles page. I tried setting the "Articles" controller parameter as optional (int? id), but then I am having problems "using" the id value inside the controller.
What is the optimal manner to handle this scenario? Perhaps I simply need a better logic for checking whether or not an id parameter was supplied in the "url"?
I am trying to avoid using two views/controllers, simply out of code-duplication reasons.
Use separate actions, like:
public ActionResult Articles() ...
public ActionResult Article(int id) ...
Alternatively move it to an Articles controller (urls using the default route will be: Articles and Articles/Detail/{id}):
public class ArticlesController : Controller
{
public ActionResult Index() ...
public ActionResult Detail(int id) ...
}
If you still must use it like you posted, try one of these:
public ActionResult Articles(int id = 0)
{
if(id == 0) {
return View(GetArticlesSummaries());
}
return View("Article", GetArticle(id));
}
public ActionResult Articles(int? id)
{
if(id == null) {
return View(GetArticlesSummaries());
}
return View("Article", GetArticle(id.Value));
}
First of all, I agree with #Steve :). But if you really want to use
int? id
you can just check in your controller method if the id is set using a simple
if(id == null)
and if so, load all articles from your DB (or something alike) and pass these to your view (either directly, or by using a view model). If the id is set you just load the article having that id from your DB and send that to the view (possibly in a list as well if you dont use view models)?
Than in your view just load all articles in the list with articles supplied to the view. Which contains either all or just one.
Complete dummy code
public ActionResult showArticles(int? id){
List<Articles> aList;
if(id == null){
aList = repository.GetAllArticles().ToList();
}else{
aList = new List<Articles>();
aList.add(repository.GetArticleByID(id));
}
return View(aList);
}
Your View has something like:
<% Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<List<Articles>>"%>
foreach(Articles a in Model)
//display article
And you call it using either of the next two options:
html.ActionLink("one article","showArticles","Articles",new {id = aID},null}
html.ActionLink("all articles","showArticles","Articles"}
Define a default value for the Id that you know indicated no value was supplied - usually 0.
public ActionResult Articles([DefaultValue(0)]int Id)
{
if (Id == 0)
// show all
else
// show selected
..
The easiest solution is to have two different actions and views but name the actions the same.
public ViewResult Articles()
{
//get main page view model
return View("MainPage", model);
}
public ViewResult Articles(int id)
{
// get article view model
return View(model);
}
this to me sounds like two separate pages and should be treated as such. You have the "Main" view page and the "articles" page.
I would split it into two actions. They should not be much dupliation at all really, both should be doing a call to get the same ModelView and the article will simply get the a extension to that!
I don't know if you tried this, but instead if you are typing the value directly into the URL, then, instead of passing it like this:
controller/action/idValue
try passing it like this:
controller/action?id=value
Make the parameter required then set a default value in the routing that is a value that isn't a valid index and indicates to your action that it should render the main page.
Well, this is the combined solution I am using:
Using same controller, with DefaultValue:
public ActionResult Articles([DefaultValue(0)]int id)
If DefaultValue was used, I refer to "MainArticles" view. If an article ID was provided - I refer to the "Articles" view with the appropriate article passed inside the ViewModel.
In both cases the ViewModel is populated with the data both views need (complete article and category lists).
Thanks everyone for your help!

Resources