Is it OK to use AJAX to POST to a Razor page model from another page? (Example) - ajax

I try to learn web development, and tested to see if I could use only the POST-side of a Razor page/model for an AJAX-script to upload a file via another razor page and at the same time not treat the "FileUpload"-page as a page.
I will cut down the code, just to show what I mean:
Index.cshtml
#* return false, to prevent rendering of FileUpload.cshtml *#
<form asp-page="./FileUpload" enctype="multipart/form-data"
onsubmit="Uploader(this);return false;" method="post">
...
<script>
async function Uploader(element) {
var result = ...
try {
const response = await fetch(element.action, {
method: 'POST',
body: formData
}).then(response => response.json())
.then(data => ...
FileUpload.cshtml.cs
// Another code checks the file and adds errors to ModelState
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
...
Example response in Index.cshtml after posting:
{"file":["File type is not allowed or the signature don't match the extenstion"]}
This works, but I wonder if it is acceptable to do this, or if there are any consequences regarding security, performance, if it is a bad/wrong way of doing it, etc?
Thanks for your time and help.

The primary purpose behind having Razor Pages as endpoints is to generate HTML. If you don't have a requirement for HTML, you should really put your upload handler in a controller, especially if the upload handler is intended to be called from multiple locations in your application.
As to the security question, you should take the same precautions with handling file upload regardless how you create the endpoint that accepts them. There is unlikely to be any significant difference in performance between a Razor page handler and a controller action.

Related

Spring-Boot: Redirect and Refresh model and page

I have a spring-boot application, with theyemleaf. I repeatedly update the page, and redirect it to the same page, so i expect that the elements of the page get updated:
#GetMapping("/suggested-events/vote/{eventId}")
public String voteForEvents(Model model,
#PathVariable("eventId") Long eventId,
#RequestParam(value = "message", required = false) String message ) {
log.info("The message is: "+message);
SuggestedEvent event = suggestedEventService.findSuggestedEventById(eventId);
ArrayList<SuggestedEvent> events = suggestedEventService.findSuggestedEventsByArea(event.getArea());
model.addAttribute("mainEvent",event);
model.addAttribute("events",events);
model.addAttribute("message",message);
return "/suggested-event/vote";
}
and when a button get pushed in the view it triggers the below post method:
#PostMapping("/suggested-events/vote")
public String voteForASuggestedEvent(RedirectAttributes redirectAttributes){
log.info("You have made a vote");
redirectAttributes.addAttribute("message", "Success");
return "redirect:/suggested-events/vote/1";
}
This second controller method, performs an operation an makes a message, and redirects it to the first method. So, it successfully redirected to the first method and it logs
log.info("The message is: "+message);
but it does not refresh my page, and i do not get the message as model?
When i redirect to the first method, i expect it adds the message to my models:
model.addAttribute("message",message);
But it does not added to my page
and when a button get pushed in the view it triggers the below post
method:
It sounds like this trigger is using AJAX, rather than a form submit. Doing so would match the symptoms you describe.
If you POST to /suggested-events/vote using AJAX, the server will return a 302, and the browser will follow it. However, the response for that 302 is still the result of an AJAX call. You have access to it in your success callback, but the browser isn't going to render it for you.
but it does not refresh my page
If a 302 doesn't cause your page to re-render, this also suggests you're using AJAX.
If you actually use a form submit instead, the browser will re-render using the markup returned by the successful redirect.
This can be verified by using the following two buttons in your vote.html:
<form action="http://localhost:8080/suggested-events/vote" method="POST">
<input type="submit" text="Submit" />
</form>
<button onclick="postmessage();" >Button</button>
<script>
function postmessage() {
$.ajax({
method: 'POST',
data: {},
url: 'http://localhost:8080/suggested-events/vote'
});
}
</script>
The first button will work as expected, and the second button matches the symptoms you describe.
If you are already using a form, please update the question with it (or better yet, the entire Thymeleaf template).
I had the same problem as OP described and Mike's explanation brought me in the right direction.
I am reading a db-table and populating it with thymeleaf using th:each. I wanted to add a javascript-confirmation before deleting an item. Sending an ajax GET without an event-listener and reloading with location.reload(true) didn't reach the #GetMapping("/delete/{id}") in the controller.
This SO-thread gave me the answer to the ajax-call.
<a class="btn btn-danger" href="#" th:onclick="|confirmDeletion('${u.id}')|"></a>
<script th:inline="javascript">
function confirmDeletion(id) {
if (confirm("Delete this id? " + id)) {
var http = new XMLHttpRequest();
http.open("GET", "/delete/" + id, true);
http.addEventListener("readystatechange", function() {
if (http.readyState === 4 && http.status === 200) {
window.location.reload(true);
}
});
http.send();
}
}
</script>
there are many ways to redirect page in Spring, but be sure if the model attribute off message its passing correctly to FrontEnd or passing like parameter to another handler , you can see this document : http://javainsimpleway.com/spring-mvc-redirecting-model-attributes-from-one-controller-to-other-controller/ , hope this is useful !!

Send form to server in jquery

I am learning ASP.NET MVC. I have to submit a to controller side after validation in client-side(in jquery). How this can be done? Should i use <form action="#" method="post"> instead of <form action="Controller/Method" method="post"> and add an event handler in click event of submit button of , to send via ajax etc? What should i do? pls help
You are on the right track, and what you suggested will work.
A better method would be to leave the original action intact, providing backwards compatibility to older browsers. You would then create the event handler as normal, and include code to prevent the default submit behavior, and use ajax instead.
$('#submitbutton').live('click', function(e){ e.preventDefault(); });
The easiest way to do this is to use the jQuery forms plugin.
This is my go-to plugin for this type of thing. Basically it will take your existing form, action url etc and convert the submission to an ajax call automatically. From the website:
The jQuery Form Plugin allows you to easily and unobtrusively upgrade
HTML forms to use AJAX. The main methods, ajaxForm and ajaxSubmit,
gather information from the form element to determine how to manage
the submit process. Both of these methods support numerous options
which allows you to have full control over how the data is submitted.
It is extremely useful for sites hosted in low cost web hosting
providers with limited features and functionality. Submitting a form
with AJAX doesn't get any easier than this!
It will also degrade gracefully if, for some reason, javascript is disabled. Take a look at the website, there are a bunch of clear examples and demos.
This is how I do:
In jQuery:
$('document').ready(function() {
$('input[name=submit]').click(function(e) {
url = 'the link';
var dataToBeSent = $("form#myForm").serialize();
$.ajax({
url : url,
data : dataToBeSent,
success : function(response) {
alert('Success');
},
error : function(request, textStatus, errorThrown) {
alert('Something bad happened');
}
});
e.preventDefault();
});
In the other page I get the variables and process them. My form is
<form name = "myForm" method = "post">//AJAX does the calling part so action is not needed.
<input type = "text" name = "fname"/>
<input type= "submit" name = "submit"/>
<FORM>
In the action page have something like this
name = Request.QueryString("fname")
UPDATE: As one of your comment in David's post, you are not sure how to send values of the form. Try the below function you will get a clear idea how this code works. serialize() method does the trick.
$('input[name=submit]').click(function(e){
var dataToBeSent = $("form#myForm").serialize();
alert(dataToBeSent);
e.preventDefault();
})

Fanycbox 1.3.4 ajax issue with ASP.NET MVC

I'm using Fancybox 1.3.4 with ASP.NET MVC 3.
I have following link :
<a id="various" href="Like/List/#feed.Id" class="petlikeCount liked">#feed.LikeCount</a>
and also jquery :
<script type="text/javascript">
$(document).ready(function () {
$("#various").fancybox({
type: 'ajax'
});
});
</script>
Controller action in Like controller :
public JsonResult List(int id)
{
return Json("success", JsonRequestBehavior.AllowGet);
}
My problem is that Like/List is never called (checked with the breakpoint) and fancybox just appears and show content of "parent" page....
I also tried with iframe content returning pure html back, but I'm getting same strange behavior as above.
Thank you in advance!
I'd recommend you using HTML helpers instead of hardcoding anchors:
#Html.ActionLink(
feed.LikeCount,
"List",
"Like",
new { id = feed.Id },
new { id = "various", #class = "petlikeCount liked" }
)
Another thing that you should make sure is that the feed.Id is actually an integer variable so that when the List action is invoked it is correctly passed this id.
So your url should look something like this: /List/Like/123. And then assuming tat you have kept the default route and haven't messed up with some custom routes, the List action should be called and passed the correct id as argument.
Also I would very strongly recommend you using a javascript debugging tool in your browser such as FireBug in which you will be able to see any potential errors with your scripts as well as the actual AJAX requests being sent which will allow you to more easily debug such problems.

jQuery Mobile/MVC: Getting the browser URL to change with RedirectToAction

My first post...
When I use RedirectToAction the url in the browser doesn't change. How can I achieve this?
I'm switching over to ASP.NET MVC 3.0 (also using jQuery Mobile) after 10+ years using web forms. I'm about 8 weeks into it, and after several books and scouring Google for an answer, I'm coming up dry.
I have a single route defined in Global.asax:
routes.MapRoute(
"Routes",
"{controller}/{action}/{id}",
new { controller = "Shopping", action = "Index", id = UrlParameter.Optional }
I have a ShoppingController with these actions:
public ActionResult Cart() {...}
public ActionResult Products(string externalId) {...}
[HttpPost]
public ActionResult Products(List<ProductModel> productModels)
{
// do stuff
return RedirectToAction("Cart");
}
The url when I do a get and post (with the post having the RedirectToAction) is always:
/Shopping/Products?ExternalId=GenAdmin
After the post and RedirectToAction I want the url in the browser to change to:
/Shopping/Cart
I've tried Redirect, and RedirectToRoute but get the same results.
Any help would be greatly appreciated.
[Update]
I found that jQuery Mobile AJAX posts are the culprit here. If I turn off jQuery Mobile's AJAX it works.
<script type="text/javascript" src="http://code.jquery.com/jquery-1.6.4.min.js"></script>
<script type="text/javascript">
// do not handle links via ajax by default
$(document).bind("mobileinit", function () { $.mobile.ajaxEnabled = false; });
</script>
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.0rc2/jquery.mobile-1.0rc2.min.css" />
The ordering of the above scripts is important. I had to include the script to jQuery first, then include the script to disable jQuery Mobile's use of AJAX and then include the script to jQuery Mobile.
I'd still like to find a way to use AJAX and have the url update properly. Or at the least be able to call jQuery Mobile's "loading" message (or bake my own).
I think I've found an answer. Buried deep in the jQuery Mobile documentation, there is information about setting the data-url on the div with data-role="page". When I do this, I get the nice jQuery Mobile AJAX stuff (page loading message, page transitions) AND I get the url in the browser updated correctly.
Essentially, this is how I'm doing it...
[HttpPost]
public ActionResult Products(...)
{
// ... add products to cart
TempData["DataUrl"] = "data-url=\"/Cart\"";
return RedirectToAction("Index", "Cart");
}
Then on my layout page I have this....
<div data-role="page" data-theme="c" #TempData["DataUrl"]>
On my HttpPost actions I now set the TempData["DataUrl"] so for those pages it gets set and is populated on the layout page. "Get" actions don't set the TempData["DataUrl"] so it doesn't get populated on the layout page in those situtations.
The only thing that doesn't quite work with this, is when you right-click... view source... in the browser, the html isn't always for the page you are on, which isn't unusual for AJAX.
Not sure if it is still actual, but in my case I wrote following helper method
public static IHtmlString GetPageUrl<TModel>(this HtmlHelper<TModel> htmlHelper, ViewContext viewContext)
{
StringBuilder urlBuilder = new StringBuilder();
urlBuilder.Append("data-url='");
urlBuilder.Append(viewContext.HttpContext.Request.Url.GetComponents(UriComponents.PathAndQuery, UriFormat.UriEscaped));
urlBuilder.Append("'");
return htmlHelper.Raw(urlBuilder.ToString());
}
And then use it as follows:
<div data-role="page" data-theme="d" #Html.GetPageUrl(ViewContext) >
This way I don't need for every redirect action store a TempData. Worked for me fine both for Redirect and RedirectToAction. This would not work properly in case if you use method "View()" inside controller and return different view name, which will change UI, but will retain url.
Hope it helps
Artem
David, this was a big help to me. I just wanted to add that in my case I had to use the following format to get the Url to display in the correct form as my other url's:
TempData["DataUrl"] = "data-url=/appName/controller/action";
return RedirectToAction("action", "controller");
As a side note, I also found that when assigning the value to TempData["DataUrl"] I was able to leave out the escaped quotes and enter it exactly as above and it seems to be working fine for me. Thanks again for your help.
There is an issue on github
https://github.com/jquery/jquery-mobile/issues/1571
It has a nice solution without the need of TempData

How do you build a Single Page Interface in ASP.NET MVC?

I want to build a webapplication with a "Single Page Interface", using ASP.NET MVC.
I have searched if this was at least possible and I think the answer is: not by simple means (reading http://msdn.microsoft.com/en-us/magazine/cc507641.aspx#S2 second-last paragraph; that article is from May 2008, though).
I found other examples which implemented this by coding/hacking with jQuery. However, I'm looking for a clean solution, using standard .NET approaches, if possible.
What I want is precisely the same functionality when you create a new "MVC Web Application". However, instead of links to "/Home/About" which reloads the entire page, I want links to "#Home/About" which loads only the new part via AJAX.
The standard approach of calling templates (partial views) with Html.RenderPartial is exactly what I want, only then loading them in through AJAX-requests.
Of course, it might be that I can't use these templates that are rendered by the Master-page for some reason (maybe it's expecting to always be called in a certain context from a certain place or so). But maybe there's another clean solution for how to build your template-pages and fetching them from the Master-page.
Who has a nice solution for implementing such thing, a Single Page Interface?
PS: I'm developing in Visual Web Developer 2008 Express Edition with MVC 1.0 installed, in C#
[edit]
Below I read that working with the templates is possible and that jQuery looks indeed like inevitable, so I tested it.
The following code transforms regular links created by Html.ActionLink into anchor-links (with #) to contain history, and then fetch the page via AJAX and only injecting the html-part I'm interested in (i.e. the partial page inside div#partialView):
$("a").each(function() {
$(this).click(function() {
$("div#partialView").load($(this).attr("href") + " div#partialView");
location.hash = $(this).attr("href");
return false;
});
});
These links also allow for graceful degredation.
But what I have left now, is still fetching the whole page instead of only the partial page. Altering the controller didn't help; it still provided me html of the whole page, with all of these statements:
public ActionResult About()
{
return View();
return View("About");
return PartialView();
return PartialView("About");
}
How could I only return the content of the part I'm interested in (i.e. the contents of Home/About.aspx)?
What I'd like is POSTing a value with AJAX (e.g. "requesttype=ajax") so that my controller knows the page is fetched via AJAX and only returns the partial page; otherwise it will return the whole page (i.e. when you visit /Home/About instead of #Home/About).
Is a good practice to alter Global.asax.cs maybe, to create a new routing schema for AJAX-calls, which will only return partial pages? (I haven't looked into this much, yet.)
[edit2]
Robert Koritnik was right: I also needed an About.ascx page (UserControl) with only the small HTML-content of that page. The first line of About.aspx was linked with the Master-page via MasterPageFile="~/..../Site.master" which caused that all HTML was printed.
But to be able to execute the following in my controller:
public ActionResult About()
{
return Request.IsAjaxRequest() ? (ActionResult)PartialView() : View();
}
I needed to alter the way a PartialView (.ascx file) and a View (.aspx) file was found, otherwise both methods would return the same page (About.aspx, ultimately resulting in an infinite loop).
After putting the following in Global.asax.cs, the correct pages will be returned with PartialView() and View():
protected void Application_Start()
{
foreach (WebFormViewEngine engine in ViewEngines.Engines.Where(c => c is WebFormViewEngine))
{
/* Normal search order:
new string[] { "~/Views/{1}/{0}.aspx",
"~/Views/{1}/{0}.ascx",
"~/Views/Shared/{0}.aspx"
"~/Views/Shared/{0}.ascx"
};
*/
// PartialViews match with .ascx files
engine.PartialViewLocationFormats = new string[] { "~/Views/{1}/{0}.ascx", "~/Views/Shared/{0}.ascx" };
// Views match with .aspx files
engine.ViewLocationFormats = new string[] { "~/Views/{1}/{0}.aspx", "~/Views/Shared/{0}.aspx" };
}
RegisterRoutes(RouteTable.Routes);
}
Full view vs. Partial view
Seems like you've messed up something. If you create an About.aspx page with all the HTML needed to display the whole page it doesn't really matter if you say
return PartialView('About');
The view still returns all the HTML that's written in it.
You should create a separate About.ascx that will only have the content of the page itself without the header and other stuff that's part of the whole page.
Your original page About.aspx will have something like this in its content (to avoid repeating writing the same content twice):
<%= Html.RenderPartial("About") %>
And you can have two controller actions. One that returns a regular view and one that returns a partial view:
return View("About"); // returns About.aspx that holds the content of the About.ascx as well
return PartialView("About"); // only returns the partial About.ascx
Regarding routes in Global.asax
Instead of writing separate routes for Ajax calls you'd rather write an action filter that will work similar as AcceptVerbsAttribute action filter. This way your requests from the client would stay the same (and thus preventing the user from manually requesting wrong stuff), but depending on the request type the correct controller action will get executed.
Well, you can load Partial View through AJAX request. In example, I'll use jquery to make an ajax call.
These could be the action in controller (named HomeController):
public ActionResult About()
{
//Do some logic...
//AboutView is the name of your partial view
return View("AboutView");
}
JQuery ajax call to place the retured html in place you want:
var resultDiv = $('#contentDIV');
$.ajax({
type: "POST",
url: "/Home/About",
success: function(responseHTML) {
resultDiv.replaceWith(responseHTML);
}
});
[edit-question is updated]
It is possible to do exactly what you want. First controller action can give you back the partial view, so mine "AboutView" could have been something like this:
<table>
<tr>
<th>
Column1Header
</th>
<th>
Column2Header
</th>
</tr>
<tr>
<td>
...
</td>
<td>
...
</td>
</tr>
and this HTML is exactly what are you going to have in responseHTML on success handler in jquery ajax method.
Second, you can distinguish in controller action if the request is an ajax request:
public ActionResult About()
{
//partial AboutView is returned if request is ajax
if (Request.IsAjaxRequest())
return View("AboutView");
else //else it will be the default view (page) for this action: "About"
return View();
}
We've got a site that does exactly this, and you really want to use the jQuery route here--alot easier to implement in the long run. And you can easily make it gracefully degrade for users who don't have javascript enabled--like google.
it isn't all that clear what are you asking for, is it for a complete example or for some specific functionality? You should be able to do this without JQuery for simple scenarios, you can use the Ajax view helpers like the ActionLink method. Also, I don't really understand what is your issue with RenderPartial, but maybe you're looking for something like RenderAction from ASP.NET MVC Futures.
ASP.NET MVC 4 (now in beta) adds support for Single Page Applications in MVC.
http://www.asp.net/single-page-application
UPDATE:
...and they removed it from the MVC 4 RC
UPDATE:
...and it is back with the 2012 Fall update

Resources