.NET & Vue API communication through [FromForm] - vue.js

I'm trying to send a list of object in which contains IFormFile.
Here are my object :
public class ImageGalleryDto
{
public Guid? PageId { get; set; }
public int? Order { get; set; }
public IFormFile? Image { get; set; }
}
My endpoint in .net core :
public async Task<EndpointResult<object>> UpdateImageGallery([FromForm] List<ImageGalleryDto> imageGalleryDto)
My object in vue is exactly same as with this model. And I'm trying to send like this in vue.js
const formData = new FormData();
formData.append("imageGalleryDto", pushToApi);
this.updateImageGallery(formData).then((res) => {....}
Unfortunately, in the controller, the list is empty when it is called.

According to your description, it looks like there is something wrong with your formdata, the formdata should like below, then the asp.net core model binding will auto bind the data to the model inside the web api controller.
const formData = new FormData();
this.images.forEach((image, index) => {
formData.append(`imageGalleryDto[${index}].pageId`, image.pageId);
formData.append(`imageGalleryDto[${index}].order`, image.order);
formData.append(`imageGalleryDto[${index}].image`, image.image);
});
The details example like below:
Since you have vue.js tag, I use vue.js example
Client:
<div id="app">
<image-upload-form></image-upload-form>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.14/dist/vue.min.js"></script>
<script>
Vue.component('image-upload-form', {
template: `
<div>
<form #submit.prevent="uploadImages" enctype="multipart/form-data">
<div v-for="(image, index) in images" :key="index">
<input type="text" v-model="image.pageId" :name="'images[index].pageId'" placeholder="Page ID"/>
<input type="number" v-model="image.order" :name="'images[index].order'" placeholder="Order"/>
<input type="file" ref="file" :name="'images[index].image'" #change="onFileChange(index,$event)" />
</div>
<button type="submit">Upload Images</button>
</form>
</div>
`,
data() {
return {
images: [{ pageId: '', order: '', image: null }, { pageId: '', order: '', image: null }]
};
},
methods: {
onFileChange(index, event) {
this.images[index].image = event.target.files[0];
},
async uploadImages() {
const formData = new FormData();
this.images.forEach((image, index) => {
formData.append(`imageGalleryDto[${index}].pageId`, image.pageId);
formData.append(`imageGalleryDto[${index}].order`, image.order);
formData.append(`imageGalleryDto[${index}].image`, image.image);
});
try {
const response = await fetch('https://localhost:7204/api/values/upload', {
method: 'POST',
body: formData
});
if (response.ok) {
console.log('Images uploaded successfully');
} else {
console.error('Failed to upload images');
}
} catch (error) {
console.error('Error uploading images', error);
}
}
}
});
new Vue({
el: '#app'
});
</script>
Backend:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
[HttpPost("upload")]
public async Task<IActionResult> UploadImages([FromForm] List<ImageGalleryDto> imageGalleryDto)
{
// Do something with the imageGalleryDtos object
return new JsonResult("OK");
}
Result:

Related

how to get data from database using ajax asp.net core

According to the following codes i have created the Get Handler in Index Model
public IActionResult OnGetCustomer(CustomerSearchModel searchModel)
{
CustomerCombine combine = new CustomerCombine
{
MyViewModels = _customerApplication.GetAll(searchModel)
};
return Partial("./Customer", combine);
}
and this is my Razor View:
#model CustomerCombine
<form asp-page="./Index" asp-page-handler="Customer"
method="get"
data-ajax="true"
data-callback=""
data-action="Refresh"
>
<div class="form-group">
<input asp-for="#Model.MySearchModel.FullName" class="form-control " placeholder="search..." />
</div>
<button class="customer-submit btn btn-success" type="submit">جستجو</button>
</form>
<table>
#foreach (var item in #Model.MyViewModels)
{
<tr>
<td>#item.Id</td>
<td>#item.FullName</td>
<td>#item.Mobile</td>
<td>#item.Email</td>
<td>#item.Address</td>
<td>#item.Image</td>
</tr>
}
</table>
My modal is displayed successfully,and i can see my database's data, but when i fill search field and click on search button , nothing happens
can any one help me plz?:)
and this is my jquey codes: i dont anything about Jquery and these codes were ready and now i dont knoe how i should use from it
$(document).on("submit",
'form[data-ajax="true"]',
function (e) {
e.preventDefault();
var form = $(this);
const method = form.attr("method").toLocaleLowerCase();
const url = form.attr("action");
var action = form.attr("data-action");
if (method === "get") {
const data = form.serializeArray();
$.get(url,
data,
function (data) {
CallBackHandler(data, action, form);
});
} else {
var formData = new FormData(this);
$.ajax({
url: url,
type: "post",
data: formData,
enctype: "multipart/form-data",
dataType: "json",
processData: false,
contentType: false,
success: function (data) {
CallBackHandler(data, action, form);
},
error: function (data) {
alert("خطایی رخ داده است. لطفا با مدیر سیستم تماس بگیرید.");
}
});
}
return false;
});
});
function CallBackHandler(data, action, form) {
switch (action) {
case "Message":
alert(data.Message);
break;
case "Refresh":
if (data.isSucceced) {
window.location.reload();
} else {
alert(data.message);
}
break;
case "RefereshList":
{
hideModal();
const refereshUrl = form.attr("data-refereshurl");
const refereshDiv = form.attr("data-refereshdiv");
get(refereshUrl, refereshDiv);
}
break;
case "setValue":
{
const element = form.data("element");
$(`#${element}`).html(data);
}
break;
default:
}
}
function get(url, refereshDiv) {
const searchModel = window.location.search;
$.get(url,
searchModel,
function (result) {
$("#" + refereshDiv).html(result);
});
}
1.The third parameter of $.get() is the success function.
2.There is no isSucceced property in your postback data. The postback data is the partial view html code. You need use $("xxx").html(data); to update the partial view code.
3.Model Binding binds the property by name, <input asp-for="#Model.MySearchModel.FullName"/> does not match the CustomerSearchModel searchModel.
public IActionResult OnGetCustomer(CustomerSearchModel MySearchModel)
Whole working demo and more details I have commented on the code pls check carefully:
Model
public class CustomerCombine
{
public List<MyViewModel> MyViewModels { get; set; }
public CustomerSearchModel MySearchModel { get; set; }
}
public class CustomerSearchModel
{
public string FullName { get; set; }
}
public class MyViewModel
{
public int Id { get; set; }
public string FullName { get; set; }
public string Email { get; set; }
public string Mobile { get; set; }
public string Address { get; set; }
public string Image { get; set; }
}
View(Index.cshtml)
#page
#model IndexModel
<!-- Button trigger modal -->
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#exampleModal">
Launch demo modal
</button>
<!-- Modal -->
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
#Html.Partial("Customer",Model.CustomerCombine)
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
#section Scripts
{
<script>
$(document).on("submit",'form[data-ajax="true"]',function (e) {
e.preventDefault();
var form = $(this);
const method = form.attr("method").toLocaleLowerCase();
const url = form.attr("action");
var action = form.attr("data-action");
if (method === "get") {
const data = form.serializeArray();
$.get(url,data,function (data) {
console.log(data); //you can check the response data in Console panel
CallBackHandler(data, action, form);
});
} else {
var formData = new FormData(this);
$.ajax({
url: url,
type: "post",
data: formData,
enctype: "multipart/form-data",
dataType: "json",
processData: false,
contentType: false,
success: function (data) {
CallBackHandler(data, action, form);
},
error: function (data) {
alert("خطایی رخ داده است. لطفا با مدیر سیستم تماس بگیرید.");
}
});
}
return false;
});
//}); //remove this.......
function CallBackHandler(data, action, form) {
switch (action) {
case "Message":
alert(data.Message);
break;
case "Refresh":
$(".modal-body").html(data); //modify here....
break;
case "RefereshList":
{
hideModal();
const refereshUrl = form.attr("data-refereshurl");
const refereshDiv = form.attr("data-refereshdiv");
get(refereshUrl, refereshDiv);
}
break;
case "setValue":
{
const element = form.data("element");
$(`#${element}`).html(data);
}
break;
default:
}
}
function get(url, refereshDiv) {
const searchModel = window.location.search;
$.get(url,
searchModel,
function (result) {
$("#" + refereshDiv).html(result);
});
}
</script>
}
Partial View does not change anything
#model CustomerCombine
<form asp-page="./Index" asp-page-handler="Customer"
method="get"
data-ajax="true"
data-callback=""
data-action="Refresh"
>
<div class="form-group">
<input asp-for="#Model.MySearchModel.FullName" class="form-control " placeholder="search..." />
</div>
<button class="customer-submit btn btn-success" type="submit">جستجو</button>
</form>
<table>
#foreach (var item in #Model.MyViewModels)
{
<tr>
<td>#item.Id</td>
<td>#item.FullName</td>
<td>#item.Mobile</td>
<td>#item.Email</td>
<td>#item.Address</td>
<td>#item.Image</td>
</tr>
}
</table>
PageModel
public IActionResult OnGetCustomer(CustomerSearchModel MySearchModel)//change here....
{
CustomerCombine combine = new CustomerCombine
{
MyViewModels = _customerApplication.GetAll(searchModel) //be sure here contains value...
};
return Partial("./Customer", combine);
}
Result:

Axios post returning 405 - Method Not Allowed (Dot Net Core 6, Razor Page & Axios)

I am facing issue with Axios, if I post simple form data it is all working fine. But if I add file input and configures Axios to post files as well, then Server returns error in response "405 - Method not allowed".
Axios configuration which works with simple data:
const httpClient = axios.create({
baseURL: document.location.origin,
headers: { 'X-Requested-With': 'XHR'}
});
Axios configuration which is NOT working in case of post files
const httpClient = axios.create({
baseURL: document.location.origin,
headers: { 'X-Requested-With': 'XHR', 'Content-Type': 'multipart/form-data'}
});
Razor Page Post Method:
public async Task<IActionResult> OnPostAsync()
{
//await Mediator.Send(Command);
return RedirectToPageJsonResult("./");
}
Cshtml snippet:
<form method="post" data-os-trigger="xhr" class="form-horizontal form-groups-bordered" enctype="multipart/form-data">
<input type="text" asp-for="#Model.Command.Status"/>
<input type="file" asp-for="#Model.Command.FileField" />
<div class="row button">
<div class="col-md-12 text-center">
<button id="btnSave" type="submit" class="btn btn-warning">Save</button>
<button type="reset" class="btn btn-light">Clear</button>
</div>
</div>
</form>
JS event which post data:
let formData = new FormData($this[0]);
let formParams = new URLSearchParams(formData);
httpClient.post($this[0].action, formParams)
.then(function (response) {
})
.catch(function (error) {
});
I found the issue with my code. If you see in my code there are two lines to get params of Form:
let formData = new FormData($this[0]);
let formParams = new URLSearchParams(formData);
I was passing formParams as argument in AXIOS call, which was causing the issue. Instead I had to use formData parameter. Once I did that, it was all working fine for me.
Here is a simple working demo you could follow:
Model
public class Command
{
public string Status { get; set; }
public IFormFile FileField { get; set; }
}
View
#page
#model IndexModel
<form asp-page="Index" method="post" data-os-trigger="xhr" class="form-horizontal form-groups-bordered" enctype="multipart/form-data">
<input type="text" asp-for="#Model.Command.Status"/>
<input type="file" asp-for="#Model.Command.FileField" />
<button id="btnSave" type="button" onclick="PostFile()" class="btn btn-warning">Save</button>
</form>
#section Scripts
{
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
function PostFile()
{
const httpClient = axios.create({
baseURL: document.location.origin,
headers: {
'X-Requested-With': 'XHR',
'Content-Type': 'multipart/form-data',
"RequestVerificationToken": $('input:hidden[name="__RequestVerificationToken"]').val()
}
});
let formData = new FormData();
formData.append("FileField", $("#Command_FileField")[0].files[0]);
formData.append("Status", $("#Command_Status").val());
httpClient.post($('form').attr('action'), formData)
.then(function (response) {
})
.catch(function (error) {
});
}
</script>
}
PageModel
public class IndexModel : PageModel
{
[BindProperty]
public Command Command { get; set; }
public void OnGet()
{
}
public async Task<IActionResult> OnPostAsync()
{
//do your stuff...
}
}

data in Vue instance doesn't get updated after axios post response

I am writing a code piece to submit the html form data on a POST REST API. Using Vue.js and axios for that.
My Vue.js code is like this -
const app = new Vue({
el: "#main-div",
data() { return {
name: 'Please enter the name',
showEdit: true,
showResponse: true,
responseText: null
}
},
methods: {
savePerson: function () {
this.showEdit = false;
axios
.post('/api/person', {
name: this.name
})
.then(function (response) {
this.responseText = response.data.name+ ' added successfully.';
console.log(response);
console.log(response.data.name+ ' added successfully.');
})
.catch(function (error) {
this.responseText = error.message;
console.log(error);
});
}
}
}
)
And html -
<div id="main-div">
<h2> Fill out the details to create a Person</h2>
<div v-if="showEdit">
<form >
<div>
Name: <input v-bind:value = 'name' type="text" v-on:focus="name= ''" />
</div>
<div>
<button v-on:click="savePerson">Save</button>
</div>
</form>
</div>
<div v-if="showResponse">
<div><p>{{ responseText }}</p></div>
<div>
<button v-on:click="showEdit = true">Add one more person</button>
</div>
</div>
This code doesn't update responseText. That I can check in Vue plugin in browser.
Any idea what is not correct in my example?
You need to use an arrow function in the callback or else the function injects its own this context:
.then((response) => {
...
})
.catch((error) => {
...
})
Or you could use async/await:
async savePerson() {
this.showEdit = false;
try {
const response = await axios.post('/api/person', {
name: this.name
})
this.responseText = response.data.name+ ' added successfully.';
} catch(error) {
this.responseText = error.message;
}
}
to bind data with the input field you need to use v-model in the HTML and try to use the arrow function in the API call.

Vue.js / How to access the function in another component

I just started learning Vue.js. I need to call the function in another component in my project. When I add new data to the table with createAnnouncement.vue, I want to go into announcement.vue and call the queryAnnouncement function. How can I do that? I would appreciate if you could help, please explain with a sample. Or edit my codes.
Announcement.Vue template:
<template>
// more div or not important template code
<div class="dataTables_filter" style="margin-bottom:10px">
<label>
<input class="form-control form-control-sm" placeholder="Search" aria-controls="m_table_1" type="search" v-model="searchField" #change="filter()">
</label>
<a style="float:right" href="#" data-target="#create-announcement-modal" data-toggle="modal" class="btn btn-primary">
<i class="">Add</i>
</a>
</div>
// more div or not important template code
</template>
Announcement.Vue Script Code:
<script>
import toastr from "toastr";
export default {
name: 'announcement',
data() {
return {
announcements: [],
searchField: "",
deleteCsrfToken: this.$root.csrfTokens["deleteAnnouncement"]
}
},
beforeMount: async function () {
await this.queryAnnouncements();
},
methods: {
filter: async function () {
await this.queryAnnouncements(this.searchField);
},
queryAnnouncements: async function (filter, pageSize, pageIndex, sortBy, sortType) {
var data = {
"query[general-filter]": filter,
"pagination[perpage]": !!pageSize ? pageSize : 10,
"pagination[page]": !!pageIndex ? pageIndex : 1,
"sort[field]": sortType,
"sort[sort]": !!sortBy ? sortBy : "asc"
};
let response = await axios
.get("/Announcement/QueryAnnouncements", { params: data })
this.announcements = response.data.data;
},
}
}
createAnnouncement.vue code:
<template>
<button #click="createAnnouncement()" v-bind:disabled="contentDetail === ''" class="btn btn-success">
Save</button>
//not important template codes
<template>
<script>
import toastr from "toastr";
export default {
name: 'create-announcement',
data() {
return {
contentDetail: "",
createCsrfToken: this.$root.csrfTokens["createAnnouncement"],
}
},
methods: {
createAnnouncement: async function () {
var self = this;
var data = {
content: this.contentDetail,
};
let response = await axios
.post("/Announcement/createAnnouncement",
data,
{
headers: {
RequestVerificationToken: self.createCsrfToken
}
})
if (response.status == 200) {
$("#create-announcement-modal .close").click()
$("#create-announcement-form").trigger('reset');
toastr["success"]("Kayıt başarıyla eklendi!", "Başarılı!");
self.contentDetail = "";
}
else {
toastr["warning"]("Hata", "Kayıt Eklenemedi.");
}
}
},
}
</script>
Please show with sample or arrangement, my english is not very good. Thanks.

knockout.js binding issue when trying to refresh data

I am using knockout.js data binding. At the page load the binding works fine and data is shown on the page correctly. Then I try to push data back to the database and the push is successful. The database receives the data OK.
The problem comes when I try to reload the data upon push success. At this time the binding already happen once (at the page load). If I don't bind it again the data on the page does not refresh. If I do the binding again knockout.js issues an error "cannot bind multiple times". If I do a cleanup before rebinding I receive an error "nodeType undefined".
Can anyone tell me what I have missed here? I am using ASP.NET MVC 4.0 with knockout.js 3.0.0.
Controller:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace MvcApplJSON.Controllers
{
public class KnockoutController : Controller
{
//
// GET: /Knockout/
public ActionResult Index()
{
return View();
}
[HttpGet]
public JsonResult GetProductList()
{
var model = new List<Product>();
try
{
using (var db = new KOEntities())
{
var product = from p in db.Products orderby p.Name select p;
model = product.ToList();
}
}
catch (Exception ex)
{ throw ex; }
return Json(model, JsonRequestBehavior.AllowGet);
}
// your controller action should return a JsonResult. There's no such thing as a controller action that returns void. You have specified dataType: 'json' so return JSON. That's it. As far as what parameter this controller action should take, well, from the JSON you are sending ({"Name":"AA"}) it should be a class that has a Name property of type string.
// modify your action signature to look like this: [HttpPost] public ActionResult SaveProduct (Product product) { ... return Json(new { success = true }); }. Or get rid of this dataType: 'json' attribute from your AJAX request if you don't want to return JSON. In this case you could return simply status code 201 (Created with empty response body): return new HttpStatusCodeResult(201);.
[HttpPost]
public ActionResult SaveProduct(Product product)
{
using (var db = new KOEntities())
{
db.Products.Add(new Product { Name = product.Name, DateCreated = DateTime.Now });
db.SaveChanges();
}
return Json(new { success = true });
}
}
}
View:
#{
ViewBag.Title = "Knockout";
}
<h2>Knockout</h2>
<h3>Load JSON Data</h3>
<div id="loadJson-custom" class="left message-info">
<h4>Products</h4>
<div id="accordion" data-bind='template: { name: "product-template", foreach: product }'>
</div>
</div>
<h3>Post JSON Data</h3>
<div id="postJjson-custom" class="left message-info">
<h4>Add Product</h4>
<input id="productName" /><br />
<button id="addProduct">Add</button>
</div>
#section Scripts {
#Scripts.Render("~/bundles/knockout")
#Scripts.Render("~/bundles/livequery")
<script src="/Scripts/jquery.livequery.min.js"></script>
<style>
#accordion { width: 400px; }
</style>
<script id="product-template" type="text/html">
<h3><a data-bind="attr: {href:'#', title: Name, class: 'product'}"><span data-bind="text: Name"></span></a></h3>
<div>
<p>Here's some into about <span data-bind="text: Name" style="font-weight: bold;"></span> </p>
</div>
</script>
<script>
var isBound;
function loadJsonData() {
$.ajax({
url: "/knockout/GetProductList",
type: "GET",
contentType: "application/json",
dataType: "json",
data: {},
async: true,
success: function (data) {
var loadJsonViewModel = {
product: ko.observableArray(),
init: function () {
loadAccordion();
}
};
var a = $('loadJson-custom');
loadJsonViewModel.product = ko.mapping.fromJS(data);
if (isBound) { }
else
{
ko.applyBindings(loadJsonViewModel, $('loadJson-custom')[0]);
isBound = true;
}
loadJsonViewModel.init();
}
});
}
// push data back to the database
function pushJsonData(productName) {
var jData = '{"Name": "' + productName + '"}';
$.ajax({
url: "/knockout/SaveProduct",
type: "POST",
contentType: "application/json",
dataType: "json",
data: jData,
async: true,
success: function (data, textStatus, jqXHR) {
console.log(textStatus + " in pushJsonData: " + data + " " + jqXHR);
//ko.cleanNode($('loadJson-custom')[0]);
loadJsonData();
},
error: function (jqXHR, textStatus, errorThrown) {
alert(textStatus + " in pushJsonData: " + errorThrown + " " + jqXHR);
}
});
}
function loadAccordion() {
$("#accordion > div").livequery(function () {
if (typeof $("#accordion").data("ui-accordion") == "undefined")
{
$("#accordion").accordion({ event: "mouseover" });
}
else
{
$("#accordion").accordion("destroy").accordion({ event: "mouseover" });
}
});
}
$(function () {
isBound = false;
loadJsonData();
$("#addProduct").click(function () {
pushJsonData($("#productName").val());
});
});
</script>
}
Here is a complete solution for your question.
I have just implemented and checked.
Please have a look.
I have created a class for getting some records ie: Records.cs.
public static class Records
{
public static IList<Student> Stud(int size)
{
IList<Student> stud = new List<Student>();
for (int i = 0; i < size; i++)
{
Student stu = new Student()
{
Name = "Name " + i,
Age = 20 + i
};
stud.Add(stu);
}
return stud;
}
}
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
}
Here is a controller for the respective view.
//
// GET: /HelpStack/
private static IList<Student> stud = Records.Stud(10);
public ActionResult HelpStactIndex()
{
return View();
}
[HttpGet]
public JsonResult GetRecord()
{
return Json(stud, JsonRequestBehavior.AllowGet);
}
[HttpPost]
public void PostData(Student model)
{
//do the required code here as All data is in "model"
}
Here is a view as HTML, I have taken two section one for list and other to Add records
<div id="loadJson-custom">
<h4>Student</h4>
<table width="100%">
<tr>
<td style="width: 50%">
<div id="accordion" data-bind='template: { name: "product-template", foreach: Student }'>
</div>
</td>
<td valign="top">
<div id="NewStudent">
<input type="text" data-bind="value: Name" /><br />
<input type="number" data-bind="value: Age" /><br />
<input type="submit" data-bind="click: Save" value="AddNew" />
</div>
</td>
</tr>
</table>
Here is your scripts for Knockoutjs.
<script id="product-template" type="text/html">
<h3><a data-bind="attr: { href: '#', title: Name, class: 'product' }"><span data-bind=" text: Name"></span></a>
<br />
Age: <span data-bind="text: Age"></span>
</h3>
<div>
<p>Here's some into about <span data-bind="text: Name" style="font-weight: bold;"></span></p>
</div>
</script>
<script type="text/javascript">
//Model for insert new record
var Model = {
Name: ko.observable(''),
Age: ko.observable(''),
};
var Student = ko.observableArray([]); // List of Students
function loadData() { //Get records
$.getJSON("/HelpStack/GetRecord", function (data) {
$.each(data, function (index, item) {
Student.push(item);
});
}, null);
}
function Save() { //Save records
$.post("/HelpStack/PostData", Model, function () { //Oncomplete i have just pushed the new record.
// Here you can also recall the LoadData() to reload all the records
//Student.push(Model);
Student.unshift(Model); // unshift , add new item at top
});
}
$(function () {
loadData();
ko.applyBindings(Model, $('#NewStudent')[0]);
});
</script>
You are declaring your model inside loadJsonData function success callback, & creating new object on every success callback, move the model outside that function, create an object & use it inside loadJsonData function, it will fix the issue.