I have a blog that has a sidebar with a partial view in it that enables users to sign up for my e-mail newsfeed. What I'm trying to do is returning the user to the page they came from after posting some data, and displaying any validation or return messages in the form's partial view.
The problem is that my partial view opens in a new window (without the lay-out). How can I fix this so it returns to my blog, with the return data in de sidebar?
This is my view:
#using Blog.Models.Entities
#model Subscriber
<header>
<h2>Subscribe</h2>
</header>
<p>Subscribe to my e-mail newsfeed.</p>
#using (Html.BeginForm("Form", "Subscription"))
{
<div class="editor-label">#Html.LabelFor(subscriber => subscriber.Email)</div>
<div class="editor-field ">#Html.EditorFor(subscriber => subscriber.Email)</div>
#Html.ValidationMessageFor(subscriber => subscriber.Email)
<input type="submit" value="Subscribe" />
<p>#ViewBag.Result</p>
}
And the relevant pieces of controller that are processing the data:
public ActionResult Form()
{
return PartialView("_Form");
}
[HttpPost]
public ActionResult Form(Subscriber subscriber)
{
if (ModelState.IsValid)
{
Subscriber foundSubscriber = _repository.Subscribers.Where(s => s.Email.Equals(subscriber.Email)).FirstOrDefault();
if (foundSubscriber != null)
{
ModelState.AddModelError("Email", "This e-mail address has already been added.");
return PartialView("_Form", subscriber);
}
_repository.SaveSubscriber(subscriber);
ViewBag.Result = "Succesfully subscribed to the newsletter.";
return PartialView("_Form");
}
ModelState.AddModelError("Email", "Please provide a valid e-mail address.");
return PartialView("_Form", subscriber);
}
When submitting a form, the browser sends an HTTP Post request to the server. The browser then displays the Response's payload. Your post Controller Action is returning a PartialView, which the browser is happily rendering (even though it doesn't have the html, head, or body tags necessary to make it truly valid HTML).
It sounds like you want the browser to keep most of your page loaded and rendered, post the form, then take the resulting HTML and only replace a portion of the loaded page. Simply put, the browser isn't smart enough to do that.
What you probably want to do is something like this:
User fills in some form data and clicks save/submit/go/whatever.
However, you don't want the browser to submit the form, because it won't preserve most of the current page the way you want.
Instead, you want the "submit" button to call some local javascript.
That local JS should bundle up the user-entered form data, craft a POST with that data as the payload, and submit the POST using Ajax. This will keep the current page loaded, while the ajax request hits your Controller Action
You controller action stays the way it is, and returns a partial view.
Your JS function that launched the Ajax call must also define a "success" function which will get called when the operation completes.
Within that success function, your javascript will grab the HTML from the response, and use it to replace the area of the page that held the original form.
I highly recommend jQuery - it will make it MUCH easier to craft the Ajax request, handle the success callback, and replace just a section of the currently-loaded page with the result. My understanding is that MS's 'unobtrubsive javascript' may also help implement this, however I don't have any direct experience with it.
Obviously, all of this will only work if the browser has javascript enabled.
I finally found the solution to the problem. I implemented it with AJAX and ended up with the following code:
_Index.cshtml
<header>
<h2>Subscribe</h2>
</header>
<p>Subscribe to my e-mail newsfeed.</p>
<div id="subscription-form">
#{Html.RenderPartial("_Form");}
</div>
_Form.cshtml
#using Blog.Models.Entities
#model Subscriber
#{
AjaxOptions ajaxOptions = new AjaxOptions
{
LoadingElementId = "loading",
LoadingElementDuration = 2000,
HttpMethod = "Post",
UpdateTargetId = "subscription-form"
};
}
<div id="loading" style="display: none;">
<p>Processing request...</p>
</div>
#using (Ajax.BeginForm("Index", "Subscription", ajaxOptions))
{
<div class="editor-label">#Html.LabelFor(subscriber => subscriber.Email)</div>
<div class="editor-field ">#Html.EditorFor(subscriber => subscriber.Email)</div>
#Html.ValidationMessageFor(subscriber => subscriber.Email)
<input type="submit" value="Subscribe" />
}
_Succes.cshtml
#using Blog.Models.Entities
#model Subscriber
<p id="subscription-result">#ViewBag.Result</p>
And the following controller action methods:
public ActionResult Index()
{
return PartialView("_Index");
}
[HttpPost]
public PartialViewResult Index(Subscriber subscriber)
{
if (ModelState.IsValid)
{
Subscriber foundSubscriber = _repository.Subscribers.Where(s => s.Email.Equals(subscriber.Email)).FirstOrDefault();
if (foundSubscriber != null)
{
ModelState.AddModelError("Email", "This e-mail address has already been added.");
return PartialView("_Form", subscriber);
}
_repository.SaveSubscriber(subscriber);
ViewBag.Result = "Succesfully subscribed to the newsletter.";
return PartialView("_Succes", subscriber);
}
ModelState.AddModelError("Email", "Please provide a valid e-mail address.");
return PartialView("_Form", subscriber);
}
I hope this will help anyone trying to achieve the same in the future. BTW, I found the solution on this blog: http://xhalent.wordpress.com/2011/02/05/using-unobtrusive-ajax-forms-in-asp-net-mvc3/.
Related
I have a asp.net core mvc project.
In my layout file, I want to display the name of the currently logged in user, such that the username is displayed in the header. For this, I want to be able to call a function in my homecontroller that does this.
So, I made a simple function taht looks like this in the home controller:
public String GetLoggedInuser()
{
return "garse garsebro";
}
And then I have tried every method I have been able to find. The first couple of methods here are just function suggested around the web, that are simply not available to me:
#HtmlHelper.Action("GetLoggedInuser");
#Html.RenderAction("GetLoggedInuser");
To name a few. Then there is this one, which I can find:
#Html.ActionLink("GetLoggedInuser")
But for this one, my function "GetLoggedInuser" can't be found anywhere.
How do you, in a razor page call a controller function that you can get returned a string from that function and display it?
If you are using Microsoft.AspNet.Identity then below line will do the work post login.
#Html.ActionLink("Hello " + User.Identity.GetUserName() + "!", "Index", "Manage", routeValues: null, htmlAttributes: new { title = "Manage" })
You can try to use ajax to call action to get the username,and add it to html:
<div id="username">
</div>
#section scripts
{
<script>
$(function () {
$.ajax({
type: "GET",
url: 'GetLoggedInuser',
}).done(function (result) {
$("#username").html(result);
});
})
</script>
}
I am using 'Pro ASP.NET MVC 5' book by Adam Freeman, working on the Sports Store Web Application that he is using. I tried to redo that example into Computer Store instead and everything was working fine up to the moment when I needed to add extra functionality to the cart ('Summary' partial view).
The 'Summary' PartialView is defined in the Cart Controller:
public PartialViewResult Summary(Cart cart)
{
return PartialView(cart);
}
The Summary View:
#model ComputerStore.Domain.Entities.Cart
#{
ViewBag.Title = "Summary";
}
<div class="navbar-right">
#Html.ActionLink("Checkout", "Index", "Cart",
new { returnUrl = Request.Url.PathAndQuery},
new { #class = "btn btn-default navbar-btn"})
</div>
<div class="navbar-text navbar-right">
<b>Your cart:</b>
#Model.Lines.Sum(x => x.Quantity) item(s),
#Model.CaluclateTotalValue().ToString("c")
</div>
And of course, in the _Layout.cshtml file, I call the Action Method:
#Html.Action("Summary", "Cart")
When I start the Web Application, I get the following error:
-A public action method 'Summary' was not found on controller 'ComputerStore.WebUI.Controllers.CartController'.
Now, before we mention HttpPost and HttpGet, let me inform you that the Sports Store example works fine using the same code as above. I have tried numerous ways to fix this, and I know that this has been answered multiple times here, but I just don't understand why it doesn't work. Moreover, all packages are updates.
You are you requiring a parameter for your Summary.
Try this instead:
[HttpGet]
public PartialViewResult Summary()
{
var cart = new Cart();
//Do something with cart to load the data.
return PartialView(cart);
}
If you want to pass a param to Summary you could do:
#Html.Action("Summary", "Cart", new {cart = someExistingCartObject})
I continue to work on a project that I do not fully understand yet. I encountered the following line of code:
<iframe id="AddDialog" style ="overflow: hidden; width:1150px; height:450px;" class="Dialogframe" scrolling="no" srcwait=#Html.Raw("'" + Url.Action("Index", "FieldChooser") + "'") srctarget=#Html.Raw("\"" + Url.Action("Index", "FieldChooser", new { ColumnFormat = false, resultmodel = Guid.Empty, datatype = "", multiselect=false }) + "\"") src=#Html.Raw("\"" + Url.Action("Loading", "FieldChooser") + "\"")></iframe>
Visual Studio tells me that srcwait and srctarget are not valid HTML5-elements, but it seems to work. The Loading View is shown for a few seconds and then the Index() method is executed (the one called in srctarget).
I am also not able to find anything on the internet about the attributes srctarget and srcwait. So what are the differences between src, srctarget and srcwait? Do they even exist or is that some invention of the person that worked on it before me?
I have a function in the FieldChooserController
[HttpPost]
public ActionResult Index(string id)
{
...
}
I want this to be called when I click on the OK button. I assumed that the srcwait part is meant for that because the call looks like that, but the function is never called.
Please bear with me and tell me if you need to see more code, at this point I have no idea what is important.
buttons: {
OK: function() {
//Save selected Value
$( this ).dialog( "close" );
if (GlobalName !=''){
addwhere(GlobalName,opts.sourceel,GlobalDefVal,GlobalDataType,GlobalValue);
}
$('#AddDialog').attr('src', $('#AddDialog').attr('srcwait'));
},
Cancel: function() {
$( this ).dialog( "close" );
$('#AddDialog').attr('src', $('#AddDialog').attr('srcwait'));
}
}
There most probably is a piece of JavaScript running, which sets the src to srcwait when an operation is performed where the user will be waiting for a wile, for example to show a loading screen.
As for your code, if you have a HttpPost annotated Index() method you wish to call upon a button click, you must create a form and let it post there:
#using (Html.BeginForm("Index", "FieldChooser", FormMethod.Post)
{
<input value="OK" type="submit" />
}
Inside a view, I have the following:
#using (Html.BeginForm())
{
<input type="submit" id="savebtn" value="Save" onclick="saveLayout()"/>
}
<script type="text/javascript">
function saveLayout() {
$.ajax({
url: '/Page/SaveFaces/',
data: {
/* layout data of the page, irrelevant */
},
type: 'post',
success: function () {
}
});
return false;
}
</script>
The above hits the following action, which simply redirects the user back to the URL they came from (it's also supposed to save the data, but I've removed that part for simplicity, as it doesn't affect the problem):
[HttpPost]
public ActionResult SaveFaces(string items)
{
return Redirect(Request.UrlReferrer.AbsoluteUri);
}
Then, due to the redirect, we go back to this pretty standard model-fetching action:
public ActionResult Index(int id = 0)
{
var page = db.Pages.Find(id);
if (page == null) return HttpNotFound();
return View(page);
}
The problem is that this last action is called twice.
I have tried removing the AJAX call and doing a normal POST operation and the problem goes away. However, the data I'm trying to send is obtainable only through the a jQuery script and I can't put them in a form. I'm constrained to work with the AJAX method.
Is there anything I can do to prevent the action from being hit twice?
I see you are using jQuery. Can you try this instead? (Note you may have to bind the the form submit event rather than the input button, or both)
#using (Html.BeginForm())
{
<input type="submit" id="savebtn" value="Save" >
}
<script type="text/javascript">
$("#savebtn").submit(function saveLayout(event) {
// The magic that prevents post.
event.preventDefault();
$.ajax({
url: '/Page/SaveFaces/',
data: {
/* layout data of the page, irrelevant */
},
type: 'post',
success: function () {
}
});
return false;
}
</script>
Also if you have access to form element, another way:
<form onsubmit="javascript: return false;">
Though it might be a bit specific to my scenario, I just found an acceptable solution. Since all the data is provided through jQuery, I removed the form completely and replaced the submit button with a simple link.
So, this goes away:
#using (Html.BeginForm())
{
<input type="submit" id="savebtn" value="Save" />
}
And this is put in place instead:
<a onclick="saveLayout()" id="saveLink">Click to save.</a>
Now the [HttpPost] action is hit, the data is saved and the redirected-to action is also hit, once.
In my view i have 10 link every link associated with some unique value. Now i want that associated value at my controller action and from that action i want to redirect the flow to some other action based on that value.
But the condition is i dont want to display it on url.
How can i acheive this?
I tried ajax.post/#Ajax.ActionLink but doing this will not facilitate redirect to another action.
Is there anything with route i need to do?
View
<ul>#foreach (var item in Model)
{<li>
#Ajax.ActionLink(item.pk_name, "Index","Candidate", new { para1= item.para1 }
, new AjaxOptions { HttpMethod = "POST" })</li>
}</ul>
Action
[HttPost]
public ActionResult(int para1)
{
return RedirectToAction(para1,"anotherController");
}
I am getting value at para1 with ajax post(that is what i primarily needed) but here also want to redirect my application flow base on para1 value which is action name.
Confision : here i am not sure is this is the right way to do this thing. So i am asking you guys should i go for route map of working with ajax post will solve my objective.
If you only need to redirect the user based on what he clicks on without showing him the link, I believe the best way to achieve this is by client-side coding.
In my opinion there is no need to take any request through the server in order to change the page for such a low-complexity redirect.
View
// HTML
// Might be broken, been awhile since I worked with MVC
// Can't really remember if that's how you put variables in HTML
<ul id="button-list">
#foreach(var item in Model)
{
<li class="buttonish" data-para1="#item.para1">#item.pk_name</li>
}
</ul>
// JS
// I wouldn't do any server related work
$('#button-list li.buttonish').click(function(){
// Your controller and action just seem to redirect to another controller and send in the parameter
window.location.href = "/controller/method/" + $(this).data('para1');
});
I think you should make one jQuery function that is call when clicked and pass unique parameter.In that function you can use AJAX and post it on appropriate controller method.
Example:
<input type="button" id="#item.pk_name" onclick="getbuttonvalue(#item.para1);" />
In script
<script type="text/javascript">
$(document).ready(function () {
function getbuttonvalue(para1) {
$.ajax({
cache: false,
type: "POST",
dataType: 'json',
url: "/controller/method/" + para1,
success: function (data) {
}
});
}
});
</script>