MVC call method on click with / without postback - c#

I have a question regarding the calling method from view.
Basically on my view I have 2 links:
1 link : When I click on it, some method should be called and executed, but nothing should change on webpage, so no postback.
2 link: When I click on it, some method should happen and postback can happen, on the same page
In controller I have:
public ActionResult FirstMethod(){ return View();}
public ActionResult SecondMethod(){ return View();}
In view:
#Html.ActionLink("Action 1", "FirstMethod", "Controller");
#Html.ActionLink("Action 2", "SecondMethod", "Controller");
So when I click on both action happens but then i get an error saying cannot find FirstMethod.chtml ..
So is this possible to have one method with postback and another one without? And how to return to the same page ... and not try to get FirstMethod.chtml ..

Following solution is based on AJAX -
Controller -
public class DemoController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpGet]
public ActionResult CallMe()
{
return new ContentResult() { Content = "This is Demo " };
}
}
Index.cshtml -
<h2>Index</h2>
<script src="~/Scripts/jquery-1.10.2.min.js"></script>
<script type="text/javascript">
$(function () {
$("#Click").click(function () {
$.ajax({
url: "/Demo/CallMe",
type: "GET",
error: function (response) {
alert(response);
},
success: function (response) {
alert(response);
}
});
});
})
</script>
<input type="button" value="Click" id="Click" />
First navigate to /demo/Index, that will display the page with above markup with a button in the page. And when we click on the Click button, we have -

The #Html.ActionLink method basically just forwards you to the specified controller-action, you cannot change this, since this is the purpose of the method.
You have to handle the click client-side, and bind a specific action to it (post some data to a url, and do nothing afterwards). One fairly easy way to do this, is to use jQuery.Post
Example from the above jquery link.
Example: Request the test.php page, but ignore the return results.
$.post("test.php");

Actually, there is no postback concept in asp.net mvc. all interactions with server should via the controller/action.
#Html.ActionLink() method just generate a link(tag a in html) and do nothing. everything happens after you send a request(such as click the link) to controller/action, if you want do nothing when click the link, you'd better use AJAX method like this
#Html.ActionLink("Action 1", "FirstMethod", "Controller", null/*routeValues*/, new { id = "link1Id" });
<script type="text/javascript">
$(function () {
$("#link1Id").click(function () {
$.get("/Contoller/FirstMethod", function () {
//do nothing or alert(something)
});
return false;
});
})
</script>

You can simply return another view after you've done what you wanted in your controller action:
public ActionResult SecondMethod()
{
//do something
return View("FirstMethod");
}
After you've seen this you will most probably be disgusted by the use of magic strings to reference views or controllers and that disgust is completely understandable :)
Then you should look whether something like T4MVC could fit your needs.

Related

Not sure how to properly use a partial view

I read a lot of questions here on stacoverflow, but it is still not clear to me how should I use a partialview within a main view with an action method. What's probably wrong is my aproach in general. With what I have so far I am not sure how to continue with my code.
I will start with the main view :
#{
ViewBag.Title = "getRolesByYear";
}
</script>
<script type="text/javascript">
getRolesForYear(parseInt(#DateTime.Now.Year));
$(function () {
$('#years a').click(function () {
var year = $(this).text();
console.log(year);
getRolesForYear(parseInt(year));
});
})
//console.log(year);
function getRolesForYear(year) {
console.log(year);
$.ajax({
type: "POST",
url: '#Url.Action("getRolesByYear", "WorkRoles")',
dataType: "json",
data: {
year: year
},
success: successFunc,
error: errorFunc
});
function successFunc(data, status) {
console.log('x');
}
}
function errorFunc() {
alert('error');
}
}
</script>
<div id = "years" class="btn-group btn-group-justified timeline">
#DateTime.Now.Year
#DateTime.Now.AddYears(-1).Year
#DateTime.Now.AddYears(-2).Year
</div>
<div id"partial"></div>
In this view I have three buttons with different year for each button. On page load or on button click I make an ajax call to an action method with the an int year as a parameter.
This is a simplified version of my action method :
public ActionResult getRolesByYear(int year)
{
// a couple of queries here
var list = list of RoleViewModel objects;
return PartialView(list);
And here is the partialView :
#model IEnumerable<eksp.Models.RoleViewModel>
#foreach (var item in Model)
{
<div class="jumbotron">
<h2>item.Role.RoleName</h2>
<h1> item.Role.RoleDescription</h1>
<p class="lead">Focus start : item.Role.FocusStart</p>
<p>Focus end : item.Role.FocusStart </p>
</div>
}
Obviously, a lot of thins aren't clear to me. How can I use this partial view with the action method i have and the main view? Do I need a separate method for the partial view? Any tips?
Your ajax call will invoke the action method which returns the partial view result (markup generated by executing the partial view). I guess you simply need to use the response of the ajax call to update your DOM.
If you want to update the content of the div with id partial with the response, you can do that in the success event handler of your ajax call.
success : function(response)
{
$("#partial").html(response);
}
I would also recommend to call the getRolesForYear method on document ready event.
$(function () {
getRolesForYear(parseInt(#DateTime.Now.Year));
$('#years a').click(function () {
var year = $(this).text();
console.log(year);
getRolesForYear(parseInt(year));
});
})
Also, If your main view is also the result of action method getRolesByYear, you probably want to return the partial view result only on the ajax call, the other calls,you can return the partial view
public ActionResult getRolesByYear(int year)
{
var list = list of RoleViewModel objects;
if(Request.IsAjaxRequest())
{
return PartialView(list);
}
else
{
return View(list);
}
}
Here the same action method handles the request for main view and ajax call. It returns the same view for ajax call, but using PartialView call, so layout will be ignored. But If you have a specific view you want to return, you can do that as well.
if(Request.IsAjaxRequest())
{
return PartialView("_yearlyRoles",list);
}
One of the reasons I like using partial views for rendering data via Ajax calls. For example if I want to start searching in order to avoid the server call i Just use an ajax call to the controller which returns the search results through a partial view. In your example yoy need to load the results in partial div.
function successFunc(data, status) {
$("#partial").html(data);
}

MVC5 simple ajax request stuck

iv read several posts about ajax calls and im still confused.
My HomeControler got methods
public async Task<ActionResult> Index(string srchterm)
public async Task Publish(TrendVM trendVm)
I want to call Publish it from index.cshtml
my view is like this
#model IEnumerable<Trend>
<div class="container-fluid post-container">
#if (Model != null)
{
foreach (var trend in #Model)
{
Html.RenderPartial("_Trend",trend);
//button that calls Publish and passes it trend without refreshing the page.
}
}
</div>
is the some razer helper that will generate the request?
Recommended approach
If you have a unique record id for each trend item you are printing, you should use that id to pass it back to your server via ajx.
foreach (var trend in #Model)
{
Html.RenderPartial("_Trend",trend);
#Html.ActionLink("Publish","Publish","Home",new { id=trend.Id},
new { #class="publishLink"})
}
Basically, the above code will render an anchor tag like this for each trend item
Publish
where 450 will be replaced with the actual unique Id you have for trend item. Clicking on the link will open the url in a new page usually. I don't think you want that to happen here. So we will override the default click behaviour and make an ajax call to server.
Add this script to your page
#section Scripts
{
<script>
$(function(){
$("a.publishLink").click(function(e){
e.preventDefault();
var url=$(this).attr("href");
$.post(url,function(response){
alert("Publish completed");
});
});
});
</script>
}
Now we need to make sure our publish method accepts an id and do the processing. So change the Publish method to /Create a new method (and use that method name in our earlier markup in Html.ActionLink call)
public async Task Publish(int id)
{
// using the Id value, do some processing.
}
But if you do not want to change your Publish method signature, what you should be doing is creating a form inside your foreach loop and serialize the form and send it. You need to keep the data you want to send in input form fields. We will keep those in hidden fields for now.
foreach (var trend in #Model)
{
Html.RenderPartial("_Trend",trend);
using(Html.BeginForm("Publish","Home"))
{
#Html.HiddenFor(s=>s.Name)
#Html.HiddenFor(s=>s.TrendCode)
#Html.ActionLink("Publish","Publish","Home",new { id=trend.Id},
new { #class="publishLink"})
}
}
Assuming Name and TrendCode are 2 properties of your TrendVM.
and the javascript will be
#section Scripts
{
<script>
$(function(){
$("a.publishLink").click(function(e){
e.preventDefault();
var _f=$(this).closest("form");
$.post(_f.attr("action"),_f.serialize(),function(response){
alert("Publish completed");
});
});
});
</script>
}
You should write some js code. And use $.ajax() function. Put a button on your View:
<button id="your-submit-button" type="submit">Ajax call</button>
Put empty div somewhere on page where you will put your PartialView:
<div id="your-partial-view-container"></div>
Then put some jquery (you also can use plain old js, but it's easier with jquery) on your page. It's better to put all your js code in #section script {} that defined in your _Layout:
$(document).ready(function() {
$("#your-submit-button").click(function(){
$.ajax({
url: #Url.Action("Publish","Home"), //here you put your controller adress
type: "POST",
dataType: 'html',
data: $("#your-form-with-model-data-id").serialize(), //that's how you get data from your form to send your TrendVM to controller
success: function(data) {
$("#your-partial-view-container").html(data);
}
});
});
});
Now when you click on button your js code should be call controller and response will be added inside your div.

ASP.NET MVC - How to call void controller method without leaving the view?

Question background:
I am implementing some basic 'shopping cart' logic to an MVC app. Currently when I click a link - denoted as 'Add To Cart' on the screen shot below this calls to an 'AddToCart' method in the 'ProductController' as shown:
Product.cshtml code:
#Html.ActionLink("Add To Cart", "AddToCart")
'AddToCart' method in the ProductController:
public void AddToCart()
{
//Logic to add item to the cart.
}
The issue:
Not an issue as such but currently when I click the 'Add To Cart' button on the ActionLink on the ProductDetail.cshtml view the page calls the 'AddToCart' method on the ProductController and gives a blank view on the page - as shown below. I want the view to stay on 'ProductDetail.cshtml' and just call the 'AddToCart' method, how do I do this?
Basically #Html.ActionLink() or <a></a> tag uses get request to locate the page. Hence whenever you clicked it, you request to your AddToCart action method in ProductController and if that action method returns null or void so a blank or empty page is shown as you experienced (because or #Html.ActionLink() get request by Default).
So if you want to add your value to cart then call AddToCart method using ajax i.e:
HTML:
#Html.ActionLink("Add To Cart", "AddToCart", null, new { id="myLink"})
Jquery or Javascript:
$("#myLink").click(function(e){
e.preventDefault();
$.ajax({
url:$(this).attr("href"), // comma here instead of semicolon
success: function(){
alert("Value Added"); // or any other indication if you want to show
}
});
});
'AddToCart' method in the ProductController:
public void AddToCart()
{
//Logic to add item to the cart.
}
Now this time when the call goes to AddToCart method it goes by using ajax hence the whole page will not redirect or change, but its an asynchronous call which execute the AddToCart action method in your ProductController and the current page will remains same. Hence the product will also added to cart and page will not change to blank.
Hope this helps.
The answer of Syed Muhammad Zeeshan is what you are looking for, however you may return an EmptyResult.
public ActionResult AddToCart()
{
//Logic to add item to the cart.
return new EmptyResult();
}
According to this it has no impact on your code ASP.Net MVC Controller Actions that return void
But maybe sometime you want to return data and then you could do something like this:
if (a)
{
return JSon(data);
}
else
{
return new EmptyResult();
}
As many people mentioned here you will need to use AJAX if your using asp.net MVC to hit a controller POST function without having to leave your view.
A good use case for this is if you want to upload a file without refreshing the page and save that on the server.
All of the
return new EmptyResult();
Wont work, they will still redirect you.
Here is how you do it, in your view have the follow form as an example:
<form enctype="multipart/form-data" id="my-form">
<p>
The CSV you want to upload:
</p>
<input type="file" class="file-upload" name="FileUpload" />
</div>
<div>
<button type="submit" class="btn btn-default" name="Submit" value="Upload">Upload</button>
</div>
</form>
Then in the JavaScript side you need to add this to your view with within Script tags.
$("#my-form").on('submit', function (event) {
event.preventDefault();
// create form data
var formData = new FormData();
//grab the file that was provided by the user
var file = $('.file-upload')[0].files[0];
// Loop through each of the selected files.
formData.append('file', file);
if (file) {
// Perform the ajax post
$.ajax({
url: '/YourController/UploadCsv',
data: formData,
processData: false,
contentType: false,
type: 'POST',
success: function (data) {
alert(data);
}
});
}
});
Your controller might look something like this to process this type of file:
[HttpPost]
public void UploadCsv()
{
var listOfObjects = new List<ObjectModel>();
var FileUpload = Request.Files[0]; //Uploaded file
//check we have a file
if (FileUpload.ContentLength > 0)
{
//Workout our file path
string fileName = Path.GetFileName(FileUpload.FileName);
string path = Path.Combine(Server.MapPath("~/App_Data/"), fileName);
//Try and upload
try
{
//save the file
FileUpload.SaveAs(path);
var sr = new StreamReader(FileUpload.InputStream);
string csvData = sr.ReadToEnd();
foreach (string r in csvData.Split('\n').Skip(1))
{
var row = r;
if (!string.IsNullOrEmpty(row))
{
//do something with your data
var dataArray = row.Split(',');
}
}
}
catch (Exception ex)
{
//Catch errors
//log an error
}
}
else
{
//log an error
}
}
There are many ways to accomplish what you want, but some of them require a lot more advanced knowledge of things like JavaScript than you seem aware of.
When you write ASP.NET MVC applications, you are required to have more intimate knowledge of how browsers interact with the web server. This happens over a protocol called HTTP. It's a simple protocol on the surface, but it has many subtle details that you need to understand to successfully write ASP.NET MVC apps. You also need to know more about Html, CSS, and JavaScript.
In your case, you are creating an anchor tag (<a href="..."/>), which when click upon, instructs the browser to navigate to the url in the href. That is why you get a different page.
If you don't want that, there are a number of ways change your application. The first would be, instead of using an ActionLink, you instead simply have a form and post values back to your current controller. And call your "add to cart" code from your post action method.
Another way would be have your AddToCart method look at the referrer header (again, part of that more subtle knowledge of http) and redirect back to that page after it has processed its work.
Yet another way would be to use Ajax, as suggested by Syed, in which data is sent to your controller asynchronously by the browser. This requires that you learn more about JavaScript.
Another option is to use an embedded iframe and have your "add to cart" be it's own page within that iframe. I wouldn't necessarily suggest that approach, but it's a possibility.
Controller should return ActionResult. In this case a redirect to the caller page.
using System.Web.Mvc;
using System.Web.Mvc.Html;
public ActionResult Index()
{
HtmlHelper helper = new HtmlHelper(new ViewContext(ControllerContext, new WebFormView(ControllerContext, "Index"), new ViewDataDictionary(), new TempDataDictionary(), new System.IO.StringWriter()), new ViewPage());
helper.RenderAction("Index2");
return View();
}
public void Index2(/*your arg*/)
{
//your code
}
I was struggling with this and couldn't get it working with ajax.
Eventually got a working solution by making my controller method return type ActionResult rather than void and returning a RedirectToAction() and inputting the action relating to the view I wanted to remain on when calling the controller method.
public ActionResult Method()
{
// logic
return RedirectToAction("ActionName");
}

After form completion redirect to temporary view, then redirect to another

I've got a simple controller:
//Begin MakeBooking
public ActionResult MakeBooking()
{
return View(new Appointment() { Date = DateTime.Now });
}
[HttpPost]
public ActionResult MakeBooking(Appointment appt)
{
//repository stuff
return View("Completed",appt);
}
//End MakeBooking
public ActionResult Index()
{
return View();
}
I'm looking for an idiomatic way to show the user the Completed view once the form has been posted, say for five seconds, and then redirect back to, say, Index/Home. Does .NET provide any native way to do this?
Does .NET provide any native way to do this?
No, But you can certainly achieve your requirement using jQuery setTimeOut function. As an example please find below script -
<script src="~/Scripts/jquery-1.10.2.min.js"></script>
<script>
$(document).ready(function () {
setTimeout(function () {
window.location.href = "#Url.Action("SubmitTag")"
}, 5000);
});
</script>
Above script will make the browser request to SubmitTag action. 5000 is the wait time in milliseconds before which the URL change code will fire.
You need to have this kind of script on your Completed view.

Creating a ActionLink that works with a javascript function

I have a datatable, on that datatable i set a Html.ActionLink. When I click that action link, I want to send an id of the item to a javascript function and have a new datatable appear below with all of its content that belongs to the selected item in the datatable above. So for example if I click a students name in a table, I want all the students Grades and Test to appear below in a separate datatable. I've never worked with javascript much so I'm not sure how I can do this. If someone can please point me in the right direction or give some tips I'd appreciate it.
original first datatable:
#foreach (var item in ((List<Epic>) ViewData["selectedestimate"]))
{
<tr>
<td>
#* #Html.ActionLink(#item.Name, "action", "controller", new {id = item})*#
#item.Name
</td>
Javascript to call:
<script type="text/javascript">
function StoryClick(story) {
$.get("#Url.Action("action", "controller")", function (response) {
$('#stories').accordion({ collapsible: true });
});
}
</script>
ActionController:
public List<EpicDetails> getEpicDetails(int id)
{
return eRepository.getItemsById(id).tolist();
}
Or do I need an ActionResult?
public Actionresult Details(int id)
{
}
I realize that I'm not even close right now, but its just b/c I'm not sure what steps to take to do this.
Eventually I would make a accordion and put the table in the accordion.
In situations like this I like to actually keep the <a> the ActionLink generates, and just add JavaScript to enhance the behavior of the link. So your view wouldn't really change (I did add a class so that we can bind an event handler to it later):
#Html.ActionLink(#item.Name, "action", "controller", new {id = item, #class = "item-link" })
Then write some jQuery (it looks like you already have a dependency on jQuery. If not, I can revise the answer to use vanilla JavaScript) to bind an event handler to links with class item-link:
<script type="text/javascript">
$(document).ready(function () {
$("a.item-link").click(function (event) {
event.preventDefault(); // Stop the browser from redirecting as it normally would
$.get(this.href, function (response) {
// Do whatever you want with the data.
});
});
});
</script>
And, yes, your action method in the controller should return an ActionResult. It's hard for me to say what type of ActionResult you should return without actually knowing what type of data you want to consume on the client, but if you wanted to inject HTML onto the page, you could write something like this:
public ActionResult Details(int id)
{
var itemDetails = /* Get details about the item */;
return PartialView("Details", itemDetails);
}
Then in your JavaScript you would write:
$("a.item-link").click(function (event) {
event.preventDefault(); // Stop the browser from redirecting as it normally would
$.get(this.href, function (response) {
$("element_to_populate").html(response);
});
});
Where element_to_populate would be a selector that points to where you want to inject the HTML.
I would highly recommend using javascript templating (I prefer handlebars.js) on the client side and returning your student data as a JsonResult. This will keep your bandwidth usage to a minimum.
But, because you seem more comfortable with razor, you could use that for all your templates, return plain html from your controller/view, and then use this javascript instead
<script type="text/javascript">
$(function() {
$("a.item-link").click(function (event) {
event.preventDefault(); // Stop the browser from redirecting as it normally would
$("#gradesContainer").load(this.href, function (response) {
//Do whatever you want, but load will already have filled up
//#gradesContainer with the html returned from your grades view
});
});
});
</script>
In your main page, below the student list, you would just need to add
<div id="gradesContainer"></div>
Your other controller would look like this
public ActionResult TestGrades(int id) {
var model = getTestGradesModel(id);
return View(model);
}
If you were returning JSON for client-side javascript templating it would look like
public ActionResult TestGrades(int id) {
var model = getTestGradesModel(id);
return new JsonResult() {Data = model}; //no view here!
}

Categories

Resources