When I click a button input which triggers an action ImagePopup inside a controller Stories but it throws an error.
Code:
#{
var listData = (List<HimHer.Models.Stories>)ViewBag.Grid;
foreach (var imageName in listData)
{
<div class="col-md-4">
#Html.HiddenFor(model => model.Story)
<input
class="img-responsive img-thumbnail"
type="image"
onclick="location.href='#Url.Action("ImagePopup", "Stories", new {story= imageName.Story})'"
src="#("/UploadedFiles/"+ imageName.Image)"
alt="Submit"
width="100%"
height="100%"/>
</div>
}
}
Upon clicking an input it throws an error:
The resource cannot be found. Requested URL: /Stories/ImagePopup
Even though it exists. It is right inside the Stories folder. It's a partial view without a model.
[HttpPost]
public ActionResult ImagePopup(string story)
{
ViewBag.PopupStyle = "";
ViewBag.PopupStory = story;
return View("GetImagesStories");
}
What am I doing wrong?
It's looking for an HTTPGet Action I believe.
If you want to call your post, you'll need to use HTML.BeginForm but it can get hairy if there are too many on a page.
Setting the href location of the current page:
location.href=
Causes the browser to do a Get Request type and does not post any form data back to your controller. Because the method on your controller specifically only works for post requests (because of the [HttpPost] attribute) there is no other matching methods that could work, thus you get an Exception.
Solutions:
You can continue using the Get method. Replace [HttpPost] with [HttpGet] will get you half way there. The other requirement will be for you to make sure the Url.Action code contains all the necessary information to be posted back (for example all the data in #Html.HiddenFor(model => model.Story) is not included, I don't know if you need it or not).
Or
You can modify your code to use the Post method. Change your <input type="image" to a <button type="submit"> and add a form around each button and hidden input element.
try using [HttpGet] attribute for your ImagePopup action method
Related
I have a 'mail server configuration' kind of view. On this view are 2 buttons:
[SOME FORM FIELDS]
<input class="button" type="submit" value="#T("Save")" />
<input class="button" type="submit" value="#T("Send Test Email")" />
The first button calls off to my controller and returns the same view with any validation/success messages (this is a form):
[HttpPost]
public ActionResult Index(MailServerSettingsViewModel viewModel)
{
...
That works brilliantly, but obviously the 'Send Test Email' button will do the same.
Assuming I don't want to come away from the page (i.e. load a different view), but want to call off to another controller to send a test e-mail, is the 'proper' way to do this to use the ActionLink helper? From this other controller can I then return this same form view? Or can I somehow use the same controller but determine which button was pressed to decide whether to validate the view model or just call off to another service/class/whatever to send the test e-mail and responding appropriately?
You can probably tell from the way I'm asking this that I come from a WebForms background and it's still a case of me getting used to what's what with MVC.
What I've Tried
For now, I'm actually calling off to this other controller asynchronously with AJAX. It actually works well, and is probably most appropriate, but this won't always be the case so I want to know how I'd achieve the above for other scenarios.
If you don't want ajax you can start a thread to send the email in your controller.
#Html.ActionLink("Send Test Email",
"actionName", "ControllerName",
new { MailServerSettingsViewModel }, new { #class = "button" })
If you set the 'name' attribute on both submit buttons to the same value, you can detect which was clicked in your controller by inspecting the value.
Let's say I have a page with 2 forms that both submit different data on the same page, one submits two ID's, and the other submits only 1 ID. They are both posting back to the same page (itself). Here is what the HTML would look like...
<form method="post">
<select name="regID">
...
</select>
<select name="jobID">
...
</select>
<input type="submit" value="Add">
</form>
<form method="post">
<button name="ID" type="submit" value="#ID">Remove</button>
</form>
Now, to handle the first form in the controller I can do
[HttpPost]
public ActionResult Index(int regID, int jobID)
{
....
}
However, if I try to handle the second form by adding
[HttpPost]
public ActionResult Index(int ID)
{
....
}
When I click the submit button, I will now get the error
The current request for action 'Index' on controller type 'UserJobController' is ambiguous between the following action methods:
System.Web.Mvc.ActionResult Index(Int32) on type careerninja.Controllers.UserJobController
System.Web.Mvc.ActionResult Index(Int32, Int32) on type careerninja.Controllers.UserJobController
So, is it possible in the controller to overload the [HttpPost] method with different values to handle 2 different sets of form data, or is this not possible? Is there another solution I might not be grasping to handle this sort of issue?
Basically, for the second form I want to have a "Remove" button that when clicked, calls the controller to remove the item, removes the item, then returns the Index() view.
I think an improvement in your design would make the problem you are having not an issue anymore. It sounds like you believe everything might have to go through your Index() method, which is not the case. Redesigning your method's name to the behavior of what the action is doing is typically how I name my methods.
Basically, for the second form I want to have a "Remove" button that when clicked, calls the controller to remove the item, removes the item, then returns the Index() view.
So create your method called Remove() and have it redirect to the Index()
public ActionResult Remove(int id)
{
// do some work
this.RedirectToAction("Index");
}
I would recommend making your method names represent what they are doing.
public ActionResult Add(int regID, int jobID)
{
// do some work
this.RedirectToAction("Index");
}
Note: This is also an important design for the User Interface. When a page typically does a POST to the server, and then the server returns HTML, if the users decides to refresh the page , a popup will typically be presented asking them if they want to resubmit data. Instead the previous examples did a server side redirect, which starts a second request as a GET and prevents the popup from occurring on refresh.
I have an application that takes some user input from a view, the reports the user wants, and creates a parameter string from it in the controller, and I need to open up multiple report urls after the query string is created but not sure how:
View snippet:
#Html.EditorFor(model => model.Id)
#Html.CheckBoxFor(model => model.report1) Report1
#Html.CheckBoxFor(model => model.report2) Report2
#Html.CheckBoxFor(model => model.report3) Report3
<input type="submit" value="Index" />
controller snippet
[HttpPost]
public ActionResult Index(ViewModel model) {
string parameters="&id="+model";
if(model.report1==true)
{
string report1="http://<urlhere>"+parameters;
}
//CONTINUE for the other two reports as well
}
I need to open the reports in multiple tabs. I have researched it extensively and it seems like you can't open multiple tabs from the controller, so I'm at a loss. I considered putting the urls in a list, passing them into a View, and using JavaScript to open them on the page load, but I honestly am not sure how to do that in Javascript and MVC.
As you already found out this is not possible from the server side, so pass the report URLs to the client and use something like the following JavaScript:
#if (!String.IsNullOrWhiteSpace(Model.ReportUri))
{
<a id="reportLink" href="#Model.ReportUri" target="_blank">REPORT</a>
<script type="text/javascript">
var link = document.getElementById('reportLink');
link.click();
link.parentNode.removeChild(link);
</script>
}
Please keep in mind, that popup-blockers will most likely block this, so you should tell this somehow to the user. Also maybe it is usefull to keep the links on the page in your case (so remove the last line of my script) to give the user the chance to manually open them when they get blocked...
I would like to use the same view for editing a blog post and adding a blog post. However, I'm having an issue with the ID. When adding a blog post, I have no need for an ID value to be posted. When model binding binds the form values to the BlogPost object in the controller, it will auto-generate the ID in entity framework entity.
When I am editing a blog post I do need a hidden form field to store the ID in so that it accompanies the next form post. Here is the view I have right now.
<% using (Html.BeginForm("CommitEditBlogPost", "Admin"))
{ %>
<% if (Model != null)
{ %>
<%: Html.HiddenFor(x => x.Id)%>
<% } %>
Title:<br />
<%: Html.TextBoxFor(x => x.Title, new { Style = "Width: 90%;" })%>
<br />
<br />
Summary:<br />
<%: Html.TextAreaFor(x => x.Summary, new { Style = "Width: 90%; Height: 50px;" }) %>
<br />
<br />
Body:<br />
<%: Html.TextAreaFor(x => x.Body, new { Style = "Height: 250px; Width: 90%;" })%>
<br />
<br />
<input type="submit" value="Submit" />
<% } %>
Right now checking if the model is coming in NULL is a great way to know if I'm editing a blog post or adding one, because when I'm adding one it will be null as it hasn't been created yet. The problem comes in when there is an error and the entity is invalid. When the controller renders the form after an invalid model the Model != null evaluates to false, even though we are editing a post and there is clearly a model. If I render the hidden input field for ID when adding a post, I get an error stating that the ID can't be null.
Edit
I went with OJ's answer for this question, however I discovered something that made me feel silly and I wanted to share it just in case anyone was having a similar issue. The page that adds/edits blogs does not even need a hidden field for id, ever. The reason is because when I go to add a blog I do a GET to this relative URL BlogProject/Admin/AddBlogPost
This URL does not contain an ID and the action method just renders the page. The page does a POST to the same URL when adding the blog post. The incoming BlogPost entity is populated by model binding and has a null Id that will be generated by EF during save changes.
Now when editing a blog post the URL is BlogProject/Admin/EditBlogPost/{Id}. This URL contains the id of the blog post and since the page is posting back to the exact same URL the id goes with the POST to the action method that executes the edit.
The only problem I encountered with this is that the action methods cannot have identical signatures.
[HttpGet]
public ViewResult EditBlogPost(int Id)
{
}
[HttpPost]
public ViewResult EditBlogPost(int Id)
{
}
The compiler will yell at you if you try to use these two methods above. It is far too convenient that the Id will be posted back when doing a Html.BeginForm() with no arguments for action or controller. So rather than change the name of the POST method I just modified the arguments to include a FormCollection.
Like this:
[HttpPost]
public ViewResult EditBlogPost(int Id, FormCollection formCollection)
{
// You can then use formCollection as the IValueProvider for UpdateModel()
// and TryUpdateModel() if you wish. I mean, you might as well use the
// argument since you're taking it.
}
The formCollection variable is filled via model binding with the same content that Request.Form would be by default. You don't have to use this collection for UpdateModel() or TryUpdateModel() but I did just so I didn't feel like that collection was pointless since it really was just to make the method signature different from its GET counterpart.
Let me know if you find a better way to make this work. The only part I'm shaky on is taking in an unnecessary variable to make the method signature different.
A few options:
Make your Id property Nullable and check for HasValue.
Add some kind of mode indicator to your ViewData and show the Hidden field depending on the value.
Put the body of the form in a partial view and include that in two different views, one with and one without the hidden field.
What I did in my project is the following:
Have a separate action and view for editing and adding
Pass along the ID of the object in the edit URL instead of in a hidden field
Use a shared editor template for the object to avoid writing the same boilerplate code twice
If you do this correctly the add and edit views will be very small.
Something pretty weird is happening with my app:
I have the following property in my ViewModel:
public int? StakeholderId { get; set; }
It gets rendered in a partial view as follows:
<%= Html.Hidden("StakeholderId", Model.StakeholderId) %>
The form is submitted, and the relevant controller action generates an id and updates the model, before returning the same view with the updated model
The problem I'm experiencing is that the hidden field does not have anything in its "value" attribute rendered the second time even though StakeholderId now has a value.
If I just output the value on its own, it shows up on the page, so I've got it to render the value by doing this:
<input type="hidden" id="StakeholderId" name="stakeholderId" value="<%: Model.StakeholderId %>" />
But it's pretty strange that the helper doesn't pick up the updated value?
(I'm using jQuery to submit forms and render the action results into divs, but I've checked and the html I get back is already wrong before jQuery does anything with it, so I don't think that has much to do with anything)
UPDATE
I've since discovered that I can also clear the relevant ModelState key before my controller action returns the partial view.
The helper will first look for POSTed values and use them. As you are posting the form it will pick up the old value of the ID. Your workaround is correct.
ADDENDUM: Multiple HTML Forms, eg, in a Grid
As an addendeum to this issue, one thing to be VERY careful of is with multiple forms on the same page, eg, in a grid, say one generated using Ajax.BeginForm.
You might be tempted to write something along the lines of:
#foreach (var username in Model.TutorUserNames)
{
<tr>
<td>
#Html.ActionLink(username, MVC.Admin.TutorEditor.Details(username))
</td>
<td>
#using (Ajax.BeginForm("DeleteTutor", "Members",
new AjaxOptions
{
UpdateTargetId = "AdminBlock",
OnBegin = "isValidPleaseWait",
LoadingElementId = "PleaseWait"
},
new { name = "DeleteTutorForm", id = "DeleteTutorForm" }))
{
<input type="submit" value="Delete" />
#Html.Hidden("TutorName", username)
}
</td>
</tr>
}
The lethal line in here is:
#Html.Hidden("TutorName", username)
... and intend to use TutorName as your action's parameter. EG:
public virtual ActionResult DeleteTutor(string TutorName){...}
If you do this, the nasty surprise you are in for is that Html.Hidden("TutorName", username) will, as Darin Dimitrov explains, render the last POSTed value. Ie, regardless of your loop, ALL the items will be rendered with the TutorName of the last deleted Tutor!
The word around, in Razor syntax is to replace the #Html.Hidden call with an explicit input tag:
<input type="hidden" id="TutorName" name="TutorName" value='#username' />
This works as expected.
Ie:
NEVER, EVER USE Html.Hidden TO PASS A PARAMETER BACK TO YOUR ACTIONS WHEN YOU ARE USING MULTIPLE FORMS IN A GRID!!!
Final Caveat:
When constructing your hidden input tag, you need to include both name and id, set to the same value, otherwise, at the time of writing (Feb 2011) it won't work properly. Certainly not in Google Chrome. All you get is a null parameter returned if you only have an id and no name attribute.