C# Inject Javascript into View with JsonResult - c#

I have a Controller Action which returns a JsonResult instead of a View. Depending on whether or not the method completed successfully, I need to add and execute a Javascript. I've added an OnActionExecuted ActionFilter to the method to generate and add the script, but because it's returning a JsonResult, I don't have a ViewResult to which I can add my script.
I'm a bit out of ideas on this. Does anyone have a solution to this problem or know of another way to approach this issue?
Controller method:
[InsertJavascript]
public async Task<ActionResult> Create(CreateAccountPageV2 currentPage, CreateAccountViewModel model)
{
//some logic here
return return Json(new
{
success = true,
redirectUrl = false,
html = partialViewToString,
invalidFields = InvalidFields
});
}
Action Filter:
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class InsertJavascriptAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var jsonResult = filterContext.Result as JsonResult;
if (jsonResult == null)
{
return;
}
var script = GenerateMyJavascript();
// some way to send the script to View and execute it
}
}
Edit: The request is made from an Ajax Form contained in a Partial View.
Partial View file where the request is made to the Controller:
#model CreateAccountViewModel
#using (Ajax.BeginForm(null, null, new AjaxOptions
{
HttpMethod = "Post",
Url = Model.Url,
OnBegin = #OnBeginId,
OnSuccess = #OnSuccessId,
OnFailure = #OnFailureId
}, new
{
#class = "form--tight",
data_component = "Auth",
data_auth_type = "create",
data_id = #guid,
}))
{
<fieldset>
// input fields
<div class="Auth-createAccount-submitContainer">
<p class="required">*#Model.RequiredFieldLabel</p>
<button type="submit" id="createFormSubmitBtn" class="btn btn-primary Auth-createAccount-submitButton">
</button>
</div>
</fieldset>
}

Related

My partial view is not being set up correctly

I have an application done in ASP.NET MVC and I am having an issue where my partial view is not being rendered after my form is submitted. What it does is that it reloads the entire page.
Here is my Model:
using System.Collections.Generic;
namespace Portfolio.Models
{
public class HomeViewModel
{
public List<User> User = new List<User>();
}
}
Here is my View:
#{
ViewBag.Title = "Home Page";
}
#model Portfolio.Models.HomeViewModel
#using (Ajax.BeginForm("FooForm",
new AjaxOptions
{
HttpMethod = "get",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "FooView"
}))
{
<button type:"submit" value:"Refresh/>
}
#Html.Partial("_FooView", Model)
<script>
window.onload = function () {
window.setTimeout(function () { $("#FooForm").submit(); }, 5000);
};
</script>
Here is my partial view:
#model Portfolio.Models.HomeViewModel
<div id="FooView">
#for (int i = 0; i < Model.User.Count; i++)
{
<form>
<div id="Name">#Model.User[i].Name</div>
<div id="Email">#Model.User[i].Email</div>
<div id="Date">#Model.User[i].Date</div>
</form>
<div>---------------------
</div>
}
</div>
Finally, here is my Controller:
using Portfolio.Models;
using System;
using System.Timers;
using System.Web.Mvc;
namespace Portfolio.Controllers
{
public class HomeController : Controller
{
public HomeViewModel model = new HomeViewModel();
Timer Timer = new Timer();
public ActionResult Index()
{
ModelState.Clear();
model.User.Add(new User() { Name = "A", Email = "a#email.com", Date = DateTime.Now.AddHours(-1) });
model.User.Add(new User() { Name = "B", Email = "b#email.com", Date = DateTime.Now.AddHours(-2) });
model.User.Add(new User() { Name = "C", Email = "c#email.com", Date = DateTime.Now.AddHours(-3) });
if (Request.IsAjaxRequest())
{
return PartialView("_FooView", model);
}
return View(model)
}
}
}
Can someone see why my Index Action is return the entire view and not the partial view upon Ajax request? Am I not setting my Ajax form properly? Many thanks in advance.
There are a few issues with your code.
First, your script does not submit your form because your form does not have an id attribute with a value of FooForm. Your Ajax.BeginForm() code does not add an id attribute (and is in fact calling a method in your controller named FooForm(). Assuming you really want to call the Index() method in your script then change the BeginForm code to
#using (Ajax.BeginForm("Index", null, new AjaxOptions(....), new { id = "FooForm" }))
which will generate <form .... id="FooForm" >
Then if your actually not hitting the code in the if (Request.IsAjaxRequest()) that returns the PartialView, it means your making a normal submit which means that you have not included jquery.unobtrusive-ajax.js in the view (or its not loading correctly or is loaded in the wrong order)
There is a missing double-quote on this line after Refresh: <button type:"submit" value:"Refresh/>

Why does Ajax.BeginForm load index again before partialview?

I am using Ajax.BeginForm to update a div with a partialview (loading logs based on the search input in the search fields).
The general idea is to load the Index the first time you log in with default values and then only update the log (partial view) when you search from there on.
The problem - When I debug my program it stops at Index in the controller before loading the partial view - resulting in long loading times.
The question - How can I make the Ajax request only load the partial view?
Code
_LogLayout.cshtml
<div id="log" class="tab">
<h1>Log</h1>
#using (Ajax.BeginForm("LogPartialView", "LogModelsController",
new AjaxOptions
{
HttpMethod = "POST",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "divLogs",
}, new
{
id = "NewTableId"
}))
{
<p>#Html.TextBox("SearchString", null, new { #placeholder = "Message" })</p>
if (Roles.IsUserInRole(WebSecurity.CurrentUserName, "Admin"))
{
<p>
#Html.DropDownList("SelectedCustomer", Model.LogModelVmObject.CustomerList, new { #id = "logdropdownlabel", #class = "dropdownlabels" })
</p>
}
<p>
<input type="submit" class="standardbutton logsearch" name="submit" value="Search" />
</p>
}
#using (Html.BeginForm("ExportData", "LogModels"))
{
<input type="submit" name="export" class="standardbutton export" value="Export to Excel" />
}
<div id="divLogs">
#Html.Raw(ViewBag.Data)
#Html.Partial("_LogPartialLayout")
</div>
</div>
</div>
LogModelsController.cs
/// <returns>
/// Returns the populated log with the current customers information if the user is of the Role Member,
/// otherwise if the user is in the Role Admin - then show all customers logs by default.
/// </returns>
public async Task<ActionResult> Index()
{
if (Session["myID"] == null)
return ExpireSession();
const int pageNumber = 1;
var lmvm = new LogModelVm { CurrentSort = null };
var myId = Convert.ToInt32(Session["myID"].ToString());
if (Roles.IsUserInRole(WebSecurity.CurrentUserName, "Admin"))
{
_customer = _cdvdb.GetAllCustomerIds();
_message = _db.GetLogs();
}
else if (Roles.IsUserInRole(WebSecurity.CurrentUserName, "Member"))
{
_message = _db.GetLogsById(myId);
}
var logs = _message.OrderByDescending(i => i.Timestamp).ToPagedList(pageNumber, PageSize);
if (Roles.IsUserInRole(WebSecurity.CurrentUserName, "Admin"))
{
if (_customer != null)
{
var selectListItems = _customer as SelectListItem[] ?? _customer.ToArray();
foreach (var log in logs)
log.Name = selectListItems.FirstOrDefault(a => a.Value == log.CustomerId.ToString())?.Text;
lmvm.CustomerList = selectListItems;
}
}
lmvm.Logs = logs;
var model = new LogStatisticsModel
{
LogModelObject = new LogModel(),
StatisticsModel = await StatisticsData.GetAllCurrentStatisticsValues(1, DateTime.Now),
LogModelVmObject = lmvm
};
return View(model);
}
/// <returns>
/// Returns a partial view of the log.
/// </returns>
[HttpPost]
public ActionResult LogPartialView(string searchString, int? selectedCustomer, string currentMessageFilter, string currentCustomerFilter, int? page, string sortOrder)
{
// Some code.
return PartialView("_LogPartialLayout", model);
}
RouteConfig.cs
using System.Web.Mvc;
using System.Web.Routing;
namespace MyProject
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute("Default", "{controller}/{action}/{id}", new { controller = "Login", action = "Index", id = UrlParameter.Optional });
routes.MapRoute("Log", "{controller}/{action}/{Id}");
routes.MapRoute("Admin", "");
}
}
}
Index.cshtml
#model MyProject.Models.LogStatisticsModel
<link href="~/Content/PagedList.css" rel="stylesheet" type="text/css" />
#{
ViewBag.Title = "MyPortal";
Layout = "~/Views/Shared/_LogLayout.cshtml";
}
After a long discussion with #StephenMuecke which (among a ton of useful things) told me to try and comment out every single javascript in the _LogLayout.cshtml file and one by one uncomment them back and see if any of them caused the problem.
I found that the following script caused the problem.
<script type="text/javascript">
$('#loadercheck').click(function ()
{
$('#loader').show();
$.ajax(
{
success: function ()
{
$('#loader').delay(1200).hide(400);
}
});
})
</script>
The following script was used to display a spinning loader while the log was loading data. Apparently this caused the Index() to be called again. So I will have to find a new way to display a loader now...

ASP MVC5 Partial view authentification redirection

I'm quite new with ASP so be tolerant :)
I've got a view with a search form in it.
View
<div id="search-form" class="row search-form">
#using(Ajax.BeginForm("Search",
"Home",
new AjaxOptions
{
UpdateTargetId = "result",
InsertionMode = InsertionMode.Replace,
LoadingElementId = "ajax_loader"
},
new { #class = "form-horizontal col-sm-offset-3 col-sm-6" }
))
{
<div class="form-group">
<div class="col-sm-10">
#{string query = string.Empty;}
#Html.EditorFor(x => query, new { htmlAttributes = new { #class = "form-control" } })
</div>
<input id="submitbtn" type="submit" value="Search" class="btn btn-primary col-sm-2" />
</div>
}
</div>
<div id="ajax_loader" style="display:none">
<img src="~/Content/Images/ajax_loader.gif" alt="Ajax Loader" />
</div>
<div id="result"></div>
Controller
[AllowAnonymous]
public ActionResult Index()
{
ViewBag.Title = "serach form";
return View();
}
public async Task<ActionResult> Search(string query)
{
WinesApiController winesCtrl = new WinesApiController();
var listOfWines = await winesCtrl.Get(query);
return PartialView("_WineResult", listOfWines);
}
The Search method in my controller returns a PartialView. When I decorate all the methodsin the controller with the [AllowAnonymous] attribute, everything works very well. But what I would like it to display the form for anybody, but as long as you click on the search button, you need to be logged in. So I deleted all the AllowAnonymous attributes but on the Index method (the one which render my View). Now, the result of my call is not shown anymore (which is quite ok) but I am not redirected to the login view.
My question is then, why the call to the partial view does not redirect me to the login view ? I guess that authentification is performed because I cannot see the results of the submit action, but why am I not redirected ?
You are making an ajax call to the Search Action right? The default asp.net mvc AuthorizeAttribute does not return an appropriate response when authorization fails on ajax calls.
You could write your own Authorization Filter, that returns a better response, like this:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public sealed class YourAuthorizeAttribute : AuthorizeAttribute {
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) {
var httpContext = filterContext.HttpContext;
var request = httpContext.Request;
var response = httpContext.Response;
if (request.IsAjaxRequest()) {
response.SuppressFormsAuthenticationRedirect = true;
response.StatusCode = (int)HttpStatusCode.Unauthorized;
response.End();
}
base.HandleUnauthorizedRequest(filterContext);
}
}
With this filter, you could have a default javascript code to handle all unauthorized scenarios:
$.ajaxSetup({
statusCode: {
401: function (response) {
var returnUrl = encodeURI(window.location.pathname + window.location.search);
var loginUrl = '/custumer/login?ReturnUrl=' + returnUrl;
window.location.href = loginUrl;
}
}
});
What happens if you put an Authorize attribute on the action?
[Authorize()]
public async Task<ActionResult> Search(string query)
{
WinesApiController winesCtrl = new WinesApiController();
var listOfWines = await winesCtrl.Get(query);
return PartialView("_WineResult", listOfWines);
}

Make Ajax.BeginForm's fire OnSuccess or OnFailure method in view based upon bool value in controller?

I don't know how the AjaxForm's will know when fire OnSuccess or OnError method. But, is is possible to make them fire OnSuccess or OnError method based on Boolean value?
#using(Ajax.BeginForm("AddAttendeeManual", "Attendee", new AjaxOptions { HttpMethod = "POST", OnSuccess = "doneManualEmail" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary()
#Html.HiddenFor(m=>m.SelectedManualEmail.AppointmentId)
<div class="form-group">
#Html.LabelFor(m => m.SelectedManualEmail.Email, new { #class = "col-md-2 control-label" })
<div class="col-md-8 input-group">
#Html.TextBoxFor(m => m.SelectedManualEmail.Email, new {#class = "form-control",PlaceHolder="Email"})
<input type="submit" id="btnManual"class="btn btn-default" value="Add>>" />
</div>
</div>
}
and this is the OnSucess method(on the same view)
function doneManualEmail() {
alert("Success");
$(#Html.IdFor(m=>m.SelectedManualEmail.Email)).val('');
var url = $("#invitedPeoples").data('url');
$.get(url, function (data) {
$('#invitedPeoples').html(data);
});
};
and this is the controller method
[HttpPost]
[ValidateAntiForgeryToken]
public void AddAttendeeManual(CreateAppointmentSelectPersons manualEmail)
{
_attendeeRepository.AddManualAttendee(manualEmail.SelectedManualEmail.AppointmentId,
manualEmail.SelectedManualEmail.Email);
}
currently when the form is submitted it calls controller's method (where person is added to database) does and after that call's the OnSuccess method mentioned above. No problem till now.
But now, I want to check something (if person exists) in controller,
this is my controller's method now
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddAttendeeManual(CreateAppointmentSelectPersons manualEmail)
{
bool result = _attendeeRepository.CheckIfAttendeeExists(manualEmail.SelectedManualEmail.AppointmentId, manualEmail.SelectedManualEmail.Email);
if(!result)
{
_attendeeRepository.AddManualAttendee(manualEmail.SelectedManualEmail.AppointmentId,
manualEmail.SelectedManualEmail.Email);
//call OnSuccess method
}
else
{
//add ModelStateError on client side?? or make it fire OnError method?
}
}
PS: There is not get method for this view. Based upon bool value I want the form to fire OnSuccess or OnError method, and if it is OnError then add a error(like a modelstate) on the clientside.
Would that be possible?
Ajax.BeginForm OnError method basically is jQuery.ajax error function and gets called when the request fails. So in your case it cannot be fired unless you manually throw an exception. In my opinion, this is a solution, but not a good one. CheckIfAttendeeExists is normal business case and should be handled by the code, not by throwing an exception.
Instead you can return a JSON that indicates whether attendee exists or not:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddAttendeeManual(CreateAppointmentSelectPersons manualEmail)
{
bool result = _attendeeRepository.CheckIfAttendeeExists(manualEmail.SelectedManualEmail.AppointmentId, manualEmail.SelectedManualEmail.Email);
if(!result)
{
_attendeeRepository.AddManualAttendee(manualEmail.SelectedManualEmail.AppointmentId,
manualEmail.SelectedManualEmail.Email);
}
return Json(new { AttendeeExists = result, ErrorMessage = "Attendee already exists" });
}
On the client check for the property:
function doneManualEmail(response) {
if(response.AttendeeExists) {
alert(response.ErrorMessage); // or something else
}
else {
alert("Success");
$(#Html.IdFor(m=>m.SelectedManualEmail.Email)).val('');
var url = $("#invitedPeoples").data('url');
$.get(url, function (data) {
$('#invitedPeoples').html(data);
});
}
};
The ModelState won't help you here, because your model is valid. You can return an error message with JSON. See the edited code.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddAttendeeManual(CreateAppointmentSelectPersons manualEmail)
{
bool result = _attendeeRepository.CheckIfAttendeeExists(manualEmail.SelectedManualEmail.AppointmentId, manualEmail.SelectedManualEmail.Email);
if(!result)
{
_attendeeRepository.AddManualAttendee(manualEmail.SelectedManualEmail.AppointmentId,
manualEmail.SelectedManualEmail.Email);
//this doesn't necessarily guarantee the onError won't get called
//there are other reasons your ajax request could fail
}
else
{
//throw an exception
throw new Exception("Ajax Call Failed!");
}
}
Also see:
ASP.NET MVC Intentional Ajax Failure

Get Button Id on the controller using Ajax.BeginForm

The Objective:
I have a post with an Ajax.BeginForm and my objective is to get The Button Id on the controller. I've seen examples using Html.BeginForm, But I need an Ajax form,
The Code: C# MVC3
View:
#using (Ajax.BeginForm("Save", "Valoration", new AjaxOptions() { HttpMethod = "Post", UpdateTargetId = "HvmDetailTabStrip", OnSuccess = "OnSuccessSaveValoration" }))
{
<div id ="HvmDetailTabStrip">
#(Html.Partial("_ValorationDetail"))
</div>
<button type="submit" style="display:none" id="db1"></button>
<button type="submit" style="display:none" id="db2"></button>
}
Controller:
[HttpPost]
public ActionResult Save(ValorationModel model)
{
if ("db1")
{
var result = ValorationService.Save(ValorationModel);
}
else
{
// ....
}
return PartialView("_ValorationDetail", ValorationModel);
}
You can get your buttons' values like this:
#using (Ajax.BeginForm("Save", "Valoration", new AjaxOptions() { HttpMethod = "Post", UpdateTargetId = "HvmDetailTabStrip", OnSuccess = "OnSuccessSaveValoration" }))
{
<div id ="HvmDetailTabStrip">
#(Html.Partial("_ValorationDetail"))
</div>
<button type="submit" name="submitButton" value="db1"></button>
<button type="submit" name="submitButton" value="db2"></button>
}
And in your controller you can write:
[HttpPost]
public ActionResult Save(ValorationModel model)
{
string buttonValue = Request["submitButton"];
if(buttonValue == "db1"){
var result = ValorationService.Save(ValorationModel);
}else
{
....
}
return PartialView("_ValorationDetail", ValorationModel);
}
Or if count of parameters you pass in method doesn't matter, you can use this:
[HttpPost]
public ActionResult Save(ValorationModel model, string submitButton)
{
if(submitButton == "db1"){
var result = ValorationService.Save(ValorationModel);
}else
{
....
}
return PartialView("_ValorationDetail", ValorationModel);
}
Other way how you can solve your problem is here ASP.Net MVC - Submit buttons with same value

Categories