How do you do a partial refresh of a page using Ajax in cshtml?
As I understand it, Ajax is required. In my scenario, in my index page I have a table where each row has a program (a batch file) and a Run button. Beneath the table I have the a space for the program output. I would like this to populate (I'm happy to wait for the program to finish just now) without having to refresh the rest of the page.
Code is below, but in summary I have one model for the table data, one model for the selected program log/output. The controller for the index page creates both and passes them in to a view model, which is passed to the view. When the Run button is hit an Index overload method in the controller handles the running of the program and 'getting' the output. It also populates the appropriate model in the VM (possibly not ideal and I'm open to suggestions to improve it).
The overloaded method currently returns a PartialViewResult and the output/logging has it's own PartialView (as I'll want to reuse it elsewhere). This is also why it has a separate model. In the PartialView break points are hit, but it doesn't appear on the page in the browser.
I'm using ASP.NET-MVC-4 with Razor.
View (Index.cshtml)
<script src="https://code.jquery.com/jquery-1.10.2.js"></script>
#model ViewModels.UpdateTestViewModel
#{ ViewBag.Title = "Update Test"; }
#{
<table>
#* Headers *#
<tr>
<td>Programs</td>
</tr>
#* Data *#
<tr>
<td>#Model.ProgramName</td>
<td style="min-width:75px"><input id="btnRun" type="button" value="Run" /></td>
</tr>
</table>
<div id="log">
#Html.Partial("ScriptLog", Model.Log)
</div>
<script>
$("input[type=button]").on("click", function () {
var NAME = ($(this).parent().siblings(":first")).text();
$.post("/UpdateTest/Run", { input: NAME });
});
</script>
}
Partial View
#model Models.ScriptLog
#if (Model != null && Model.Log.Any(x => !string.IsNullOrWhiteSpace(x)))
{
<fieldset>
<legend>Log</legend>
#foreach (string entry in Model.Log)
{
<p>#entry</p>
}
</fieldset>
}
Script Log
public IEnumerable<string> Log { get { // returns log } }
ViewModel
public class UpdateTestViewModel
{
public string ProgramName { get { return "My Program"; } }
public ScriptLog Log { get { return _log; } }
private readonly ScriptLog _log;
public UpdateTestViewModel(ScriptLog log)
{
_log = log;
}
}
Controller
public ActionResult Index()
{
if (SessionFacade.CurrentUpdateTestLog == null)
{
ScriptLog log = new ScriptLog();
SessionFacade.CurrentUpdateTestLog = log; // Store in Session
}
UpdateTestViewModel vm = new UpdateTestViewModel(SessionFacade.CurrentUpdateTestLog);
return View(vm);
}
[ActionName("Run")]
public PartialViewResult Index(string input)
{
ExecuteScript.ExecuteUpdateTestScript(input); // Run batch file
UpdateTestLog(input); // Get log and update in Session
return PartialView("ScriptLog", SessionFacade.CurrentUpdateTestLog);
}
Since you are making an $.post() you need to decorate your /UpdateTest/Run action with [HttpPost].
You also don't define a success handler so, while you are making the request, you never do anything with it.
$.post("/UpdateTest/Run", { input: NAME })
.done(function(partialResult) {
$("#log").html(partialResult);
})
.fail(function(jqxhr, status, error) {
console.log(jqXhr, status, error);
});
With much help and patience from #Jasen I got a working solution which was to extend the existing Ajax, so that it looks like:
$("input[type=button]").on("click", function () {
var NAME = ($(this).parent().siblings(":first")).text();
$.post("/UpdateTest/Run", { input: NAME })
.done(function (partialResult) {
$("#log").html(partialResult);
})
});
Note I also have added the [HttpPost] attribute in the controller
Related
I'm trying this code:-
If no query string supplied to the Index Method then render a Branch Locator View. When a Branch Id is selected in that View, post back to a Redirect To Route Result OR Action Result method and then redirect back to Index with a query string of the selected Branch Id.
I can run through the code successfully without and then with the query string.
I even run through the Index View and can see the Model working however, the Index View does not render, the Branch Selector View remains. Network developer tools shows the correct URL with query string correctly in place when doing the Redirect.
(NOTE: Both methods are on the same controller).
If I add the same query string directly in the Browser address bar it works fine!
I have this code:
[HttpGet]
public ActionResult Index()
{
var querystringbranchId = Request.QueryString["branchId"];
if(!string.IsNullOrEmpty(querystringId))
{
....do stuff like build a model using the branchId...
return View(Model);
}
return View("BranchSelector")
}
[HttpPost]
public RedirectToRouteResult BranchDetails(FormCollection formCollection)
{
var querystringBranchId = formCollection["BranchList"];
var branchId = int.Parse(querystringBranchId);
return RedirectToAction("Index", new { branchId });
}
Try using strongly typed model on the post, and specifying the param as an actual param - Using View models is going to be much better for you.
I have tested the below - It seemed to work as expected for me:
[HttpGet]
public ActionResult Index(int? branchId)
{
if (branchId.HasValue)
{
return View(branchId);
}
return View("BranchSelector");
}
[HttpPost]
public RedirectToRouteResult BranchDetails(MyModel myModel)
{
return RedirectToAction("Index", new { myModel.BranchId });
}
public class MyModel
{
public int BranchId { get; set; }
}
The View:
<div>
#using (Html.BeginForm("BranchDetails", "Home", FormMethod.Post))
{
#Html.TextBox("BranchId","123")
<input type="submit" value="Go"/>
}
</div>
#MichaelLake Thanks to your post I found the problem. I tried your code and sure enough it works as expected. I didn't mention I was using a Kendo Combobox control (!) loaded with the branches. I didn't mention that as the actual data I needed was available in the post method so, thought the issue was with the Controller methods. I had the Kendo control name as BranchList, I changed it to BranchId and it now works with the original code as expected! The Kendo name becomes the element Id and has to match to work.
Many Thanks!
This will work for you. Cheers :D
return RedirectToAction("Index", "ControllerName", new { branchId = branchId});
In my Index view, I render(action) 3 other views, Create, Edit and List. The Create and Edit views are both forms to manipulate data in a database. When I push edit button in the List View the Create form should disappears and the Edit form should appear. So Create and Edit shouldn't be shown at the same time.
When I don't render the views in one view, everything works fine. But because I don't want to switch pages, I want to render the views in one page. Here is the problem. My Create form and List view works fine when I render them in one page. But when I also want to render my Edit form, I get errors.
One thing that happens is that the Create form puts de data in the database 2 times. Also when I don't put validated data in my form, the edit form appears at the same time that the create form is shown. The third problem is when I put the edit button in my List. The create form disappears and the edit form apears. So this works, but when I want to save the changed data, I get a hex string error. When I restart the application, the data isn't changed, but there is new record with the changed data.
I guess there is a problem with the id string. But I can't fix it.
So I have 4 views (index, create, list and edit)
Create, list and edit all have their own model.
And I use one controller for the views / models
The controller (CarsController):
public class CarsController : BaseController
{
public ActionResult Index()
{
return View();
}
public ActionResult List()
{
List<Car> carsInDb = CarRentalContext.Cars.FindAll().ToList();
return View(carsInDb.ConvertAllToViewModels());
}
[HttpGet]
public ActionResult Create(string id)
{
if (String.IsNullOrEmpty(id))
{
return View();
}
return null;
}
[HttpPost]
public ActionResult Create(InsertCarViewModel insertCarViewModel)
{
if (ModelState.IsValid)
{
Car car = insertCarViewModel.ConvertToDomain();
CarRentalContext.Cars.Insert(car);
Response.Redirect("Cars", true);
}
return View(insertCarViewModel);
}
[HttpGet]
public ActionResult Edit(string id)
{
if (String.IsNullOrEmpty(id))
{
return null;
}
Car car = CarRentalContext.Cars.FindOneById(new ObjectId(id));
return View(car.ConvertToUpdateViewModel());
}
[HttpPost]
public ActionResult Edit(UpdateCarViewModel updateCarViewModel)
{
if (ModelState.IsValid)
{
Car modifiedCar = updateCarViewModel.ConvertToDomain();
CarRentalContext.Cars.Save(modifiedCar);
return RedirectToAction("Index");
}
return View(updateCarViewModel);
}
}
The index view:
#model MvcApplication1.ViewModels.UpdateCarViewModel
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
<div>
#{Html.RenderAction("Create", Model);}
</div>
<div>
#{Html.RenderAction("Edit", Model);}
</div>
<div>
#{Html.RenderAction("List", Model);}
</div>
</body>
</html>
Instead of show/hide the views for create and edit views try to replace one with another, using ajax to get the result of the action. This way you will have only one view in the page.
Do something like this:
<script type="text/javascript">
function renderActionInDiv(idcar, action)
{
$.ajax({
type: "POST",
url: action, //or "/Cars/Edit"
data: { id = idcar},
success: function (ajxData) {
if (typeof callback !== 'undefined') {
$("#div-editOrCreate").html(ajaxData);
}
},
async: asyncCall
});
}
</script>
<a onclick='renderActionInDiv(3, "Edit")'>edit car<a/>
<a onclick='renderActionInDiv(0, "Create")'>create new car<a/>
<div id="div-editOrCreate">
#{Html.RenderAction("Create", Model);}
</div>
I have a simple ASP.NET MVC 3 dummy app (just learning MVC coming from WebForms).
And I'm pretty confused about how to update a form without actually having some DB stuff in between. I just have a form with a textbox and after I press the button I want to see the string in uppercase. But my nothing happens.
The controller:
public ActionResult Index()
{
ToUppercaseModel model = new ToUppercaseModel { TheString = "testing" };
return View(model);
}
[HttpPost]
public ActionResult Index(ToUppercaseModel model)
{
model.TheString = model.TheString.ToUpper();
return View(model);
}
The model:
public class ToUppercaseModel
{
[Display(Name = "My String")]
public string TheString { get; set; }
}
And the view:
#using (Html.BeginForm()) {
<div>
<div class="editor-label">
#Html.LabelFor(m => m.TheString)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.TheString)
</div>
<p>
<input type="submit" value="Convert" />
</p>
</div>
}
This is a simple as it gets I think. Now obviously the return View(model); in the 2nd Index method is not working. I saw some stuff about RedirectToAction() and storing the data in TempData. Most example just submit some id, but since I don't have db that does not work.
If I do this:
return RedirectToAction("Index", model);
I get a
No parameterless constructor defined for this object.
Error Message. This should be simple, no? I think I understand the Post/Redirect/Get concept, but don't see how to apply it for something simple as this.
Thanks for some clarification.
When MVC renders a view it will use the attempted value of a field rather than the model's value if it exists (eg in a datefield I put "Tuesday", this won't model bind but you'll want to show the user the field with their input and highlighted as invalid), you're changing the model's value but not the attempted value.
The attempted value is held in the modelstate dictionary:
ModelState["KeyToMyValue"].Value.Value.AttemptedValue
Accessing and changing these values can be tricky unless you want a load of magic strings in your code, and as validation happens on modelbinding your changed value won't be validated.
My recommendation in these circumstances is to call ModelState.Clear(), this will remove all validation and attempted values, then change your model directly. Finally you want to get your validation on the model by using TryValidateModel(yourModel).
Be aware that this method is probably the easiest non-hacky method of doing this but will remove attempted values that could not bind from the returned view.
You have to call 1 method from the 2 method, but you have to change it
public ActionResult Index(ToUppercaseModel model)
and send to 1 method your model.
public ActionResult Index(ToUppercaseModel? model)
{
if (model == null)
ToUppercaseModel model = new ToUppercaseModel { TheString = "testing" };
return View(model);
}
I think I got a solution, it works, but not sure if this is the way it should be?
Basically I just put my model into the TempData and call the normal Index method again.
public ActionResult Index()
{
ToUppercaseModel model = null;
if (TempData["FeaturedProduct"] == null)
{
model = new ToUppercaseModel { TheString = "testing" };
}
else
{
model = (ToUppercaseModel)TempData["FeaturedProduct"];
}
return View(model);
}
[HttpPost]
public ActionResult Index(ToUppercaseModel model)
{
model.TheString = model.TheString.ToUpper();
TempData["FeaturedProduct"] = model;
//return View(model);
return RedirectToAction("Index");
}
I have a contact page and this page shall either show a form or a success message or a failure message, so basically something like this:
#model MyApp.Models.ContactData
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
<div>
...Some static content...
If page was opened the first time
-> Render a form here
Else If form was posted and data successfully processed
-> Render a success message here
Else If form was posted but error occurred during processing
-> Render a failure message here
...Some static content...
</div>
I don't know what's the best way to achieve this with MVC 3. Do I create three completely separate views (which is something I'd like to avoid because of the static content which would be the same for all three views)? Or could I create three partial views and then decide based on an additional flag I could put into the model class which partial view to render? Or can I inject somehow the partial views dynamically from the controller into the view?
The controller I have so far looks like this:
public class ContactController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(ContactData contactData)
{
if (ModelState.IsValid)
{
ContactService service = new ContactService();
bool result = service.Process(contactData);
return ?; // What do I return now? It must somehow depend on result.
}
else
return View(contactData));
}
}
I had a similar page and behaviour with ASP.NET WebForms and the solution was there to put the three variable blocks of markup into asp:Panel controls and then switch on or off the Visible flag of those panels from code-behind. I guess I need quite another approach with ASP.NET MVC to reach the same goal.
What is the best way?
Thank you for suggestions in advance!
You can try this way:
[HttpPost]
public ActionResult Index(Contact contactData)
{
if (ModelState.IsValid)
{
ContactService service = new ContactService();
if (service.Process(contactData))
{
TempData["Success"] = "Your success message.";
return RedirectToAction("Index");
}
else
{
TempData["Error"] = "Your fail message.";
}
}
return View(contact);
}
Perhaps use the ViewBag to help achieve all this. Of course it's a dynamic, so you can add & check for any prop you want/need/expect.
[HttpPost]
public ActionResult Index(ContactData contactData)
{
if (ModelState.IsValid)
{
ContactService service = new ContactService();
bool result = service.Process(contactData);
ViewBag.ContactSuccess = true;
}
else
{
ViewBag.ModelStateErr= "some err";
}
return View(contactData));
}
Then in your View:
if (ViewBag.ContactSuccess !=null && ((bool)ViewBag.ContactSuccess))
{
//thanks for posting!
}
else
{
if (ViewBag.ModelStateErr !=null)
{
//show that we have an err
}
else
{
//we have no err nor a 'true' contact success yet
//write out the form
}
}
Looks like that you can issue an ajax call on the client side, and based on the Json result, you can render different content from the client side.
I'd suggest coding up three different Views
index.cshtml
contactSuccess.cshtml
contactFail.cshtml
Then in your Controller, you'll have similar code as before
public class ContactController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(ContactData contactData)
{
if (ModelState.IsValid)
{
ContactService service = new ContactService();
bool result = service.Process(contactData);
return View("contactSuccess.cshtml");
}
else
return View("contactFail.cshtml", contactData);
}
}
This way each view has an independent and you don't have a big inline IF block in the middle of your markup.
Alternatively (and this is how I'd do it) you can have the index.cshtml contain three partials...
_ContactForm.cshtml
_ContactSuccess.cshtml
_ContactFail.cshtml
and then you can load the partial views into the index view, and even swap them out dynamically using AJAX.
I have a view called Associations that has a drop down list and a button. When the user selects an option and presses submit, I want them to go to Association/{associationKey}. Association needs to work on get and post.
Currently, with the code below, when the form is posted, it DOES go to Association and displays the correct record, BUT it does not append the associationKey to the url.
So I am getting:
http://localhost/Association
instead of:
http://localhost/Association/202
If I manually navigate to http://localhost/Association/202 everything works perfectly, so get and post are both working fine....I just want the key in the url after a post!
Surely there must be something super simple I am doing wrong. Relevant code below.
Thanks!
ASSOCIATIONS view:
<% Html.BeginForm("Association", "Staff", FormMethod.Post); %>
<%:Html.DropDownList("associationKey", new SelectList(Model.Associations.ToList(), "AssociationKey", "LegalName"))%>
<input type="submit" value="Edit The Selected Record" />
<% Html.EndForm(); %>
STAFF controller:
[AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)]
public ActionResult Association(int associationKey)
{
return View("Association", new AssociationViewModel(associationKey));
}
GLOBAL.asax:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute("Default", "{action}", new { controller = "Staff", action = "Default" });
routes.MapRoute("Associations", "Associations", new { controller = "Staff", action = "Associations" });
routes.MapRoute("Association", "Association/{associationKey}", new { controller = "Staff", action = "Association" });
}
ASSOCIATION view model:
public class AssociationViewModel
{
public Repository db = new Repository();
public Association Association {get; private set; }
public List TelephoneTypes { get; private set; }
public AssociationViewModel(int associationKey)
{
Association = db.AssociationById(associationKey);
TelephoneTypes = db.ListTelephoneTypes().ToList();
}
}
I think you should separate out your controller actions into a Get action and a POST action like so:
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Association(int associationKey)
{
return View("Association", new AssociationViewModel(associationKey));
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Association(AssociationViewModel model)
{
return RedirectToAction("Association", new {associationKey= model.associationKey});
}
The MVC framework will automatically bind the selected value from your SelectList to the model (assuming you have a property in the model to hold the selected value). From there you just need to redirect to your GET method passing in the key.
It's doing a post instead of a GET. This puts the value in the form parameters not in the url. You might want to intercept the form submit using javascript and turn it into a GET using location=..
<script type="text/javascript">
$(function() { // requires jQuery
$('form').submit( function() {
// maybe do some validation to ensure a legal value chosen first?
location.href = $(this).attr('action') + '/' + $(this).find('select').value();
return false; // cancel submit
});
});
</script>
<% Html.BeginForm("Association", "Staff", FormMethod.Post); %>
<%:Html.DropDownList("associationKey", new SelectList(Model.Associations.ToList(), "AssociationKey", "LegalName"))%>
<input type="submit" value="Edit The Selected Record" />
<% Html.EndForm(); %>