Server side code:
public class SomeModel
{
public Int64 Id { get; set; }
[Required]
public Int64 From_UserId { get; set; }
public string Text { get; set; }
public List<HttpPostedFileBase> Files {get; set;} //<-- Wonder if this is right way ?
}
Action Method in Controller
[HttpPost]
[Route("Upload")]
public IHttpActionResult Upload( SomeModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
//More code
return Ok();
}
Would angular client code like this work?
$http.post("api/upload",{
Id: 1,
From_UserId: 1,
Text: "First File",
Files: [file1, file2, file3] //<-These are the ones obtained through file type input
})
Additional Info: Using Azure Storage to store files uploaded.
Here is the great directive for it. ng-file-upload
Here is the Demo using Asp.net WebApi.
JS
//inject directives and services.
var app = angular.module('fileUpload', ['ngFileUpload']);
app.controller('MyCtrl', ['$scope', 'Upload', function ($scope, Upload) {
// upload later on form submit or something similar
$scope.submit = function() {
if ($scope.form.file.$valid && $scope.file) {
$scope.upload($scope.file);
}
};
// upload on file select or drop
$scope.upload = function (file) {
Upload.upload({
url: 'upload/url',
data: {file: file, 'username': $scope.username}
}).then(function (resp) {
console.log('Success ' + resp.config.data.file.name + 'uploaded. Response: ' + resp.data);
}, function (resp) {
console.log('Error status: ' + resp.status);
}, function (evt) {
var progressPercentage = parseInt(100.0 * evt.loaded / evt.total);
console.log('progress: ' + progressPercentage + '% ' + evt.config.data.file.name);
});
};
// for multiple files:
$scope.uploadFiles = function (files) {
if (files && files.length) {
for (var i = 0; i < files.length; i++) {
Upload.upload({..., data: {file: files[i]}, ...})...;
}
// or send them all together for HTML5 browsers:
Upload.upload({..., data: {file: files}, ...})...;
}
}
}]);
Related
I can't find an answer here to this one (been asked before, but this is specific to MVC5 WebAPI2.1 and jQuery), but in Postman, I'm getting
{
"Message": "No HTTP resource was found that matches the request URI 'https://localhost:44371/api/reportdiscrepancy'.",
"MessageDetail": "No action was found on the controller 'ReportProblem' that matches the request." }
error message - which is there (I have other controllers that use GET, but this one is using POST is the only difference and they work just fine). My controller (note: this inherits from ApiController, NOT Controller) - and the code worked fine when it was an ASMX :
[Route("api/reportdiscrepancy")]
[HttpPost]
public async Task<IHttpActionResult> ReportDiscrepancy(string lid, string ProblemTypes, string ProblemTypeOther)
{
string retval = string.Empty;
lid = AppCore.Validation.StripToGUID(lid);
ProblemTypes = AppCore.Validation.StripToDigitInCSVFromStr(ProblemTypes);
ProblemTypeOther = AppCore.Validation.StripAllCharsForMetaTag(StripHtml(ProblemTypeOther));
var session = HttpContext.Current.Session;
if (lid.Length == 36 && ProblemTypes.Length > 0 | ProblemTypeOther.Length > 0)
{
if (IsGUID(lid))
{
if (session != null)
{
if (session.SessionID != null)
{
if (session.SessionID.Length > 0)
{
var dr = new Discrepancy
{
lid = lid,
SessionId = session.SessionID,
IPAddress = Ops.Site.GetUserIP(HttpContext.Current),
UserId = "anonymous"
};
if (User != null)
{
if (User.Identity != null)
{
if (User.Identity.IsAuthenticated)
{
dr.UserId = User.Identity.GetUserId();
}
}
}
dr.ProblemTypeIds = ProblemTypes;
dr.ProblemTypeOther = ProblemTypeOther;
dr.idx = await DiscrepancyReportData.InsertDiscrepancyAsync(dr);
if (dr.idx > 0)
{
retval = "success";
}
}
}
}
}
}
return Ok(retval);
}
The jquery/javascript:
$("#btnSendReport").bind('click', function (e) {
var parElem = $(this).parent().parent().parent();
var listID = parElem.find("#hidLID").val();
var problemTypes = $.map(parElem.find('option:selected'), function (e) { return e.value; }).join(',');
var problemTypeOther = parElem.find("#txtProblemTypeOther").val();
var obj = {};
obj.lid = listID;
obj.ProblemTypes = problemTypes;
obj.ProblemTypeOther = problemTypeOther;
try {
$.ajax({
type: "POST",
url: "../../api/reportdiscrepancy/",
data: JSON.stringify(obj), //not sure if this is necessary w/webAPI
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (data) {
var result = data; //not doing anything here
},
error: function (err) {
console.log(err);
}
});
} catch (err) {
console.log(err);
}
});
Anyone have any ideas? There are no examples with WebAPI 2.1 showing a jQuery post (I don't want to use GET because of query string limits and I'm not using a FORM or BODY, so those answers won't work either). The more I look into this, I get many answers, but all very specific to special cases. I would like to just see one working example of POSTing and OBJECT (not a string or anything simple) from JQuery (3.x or higher) to an MVC5 (other versions are different) and NOT Core (totally different) and WebAPI 2.x .
How to properly decorate the controller for a jquery post (not a querystring, not a form).
Any ideas?
fix the action route by adding ~. The route will start from root
[Route("~/api/reportdiscrepancy")]
fix ajax url to start from root too. also remove stringify and contentType: "application/json; charset=utf-8", since I am not sure that your ancient framework supports it
$.ajax({
type: "POST",
url: "/api/reportdiscrepancy",
data: obj,
dataType: "json",
but I would create a special class for an action too
public class ViewModel
{
public string Lid {get; set;}
public string ProblemTypes {get; set; }
public string ProblemTypeOther {get; set;}
}
[Route("~/api/reportdiscrepancy")]
public async Task<IHttpActionResult> ReportDiscrepancy(ViewModel model)
{
.....
}
On the client side - I made the changes Serge recommended.
$("#btnSendReport").bind('click', function (e) {
var parElem = $(this).parent().parent().parent();
var listID = parElem.find("#hidLID").val();
var problemTypes = $.map(parElem.find('option:selected'), function (e) { return e.value; }).join(',');
var problemTypeOther = parElem.find("#txtProblemTypeOther").val();
if (problemTypeOther === null) { problemTypeOther = '' };
var obj = {};
obj.lid = listID;
obj.ProblemTypes = problemTypes;
obj.ProblemTypeOther = problemTypeOther;
try {
$.ajax({
type: "POST",
url: "../../api/reportdiscrepancy/",
data: obj,
dataType: "json",
success: function (data) {
var result = data;
},
error: function (err) {
console.log(err);
}
});
} catch (err) {
console.log(err);
}
});
In the api controller, the only change required was the inputs to an object (webapi 2.x does automatic conversion - didn't know this)
[Route("api/reportdiscrepancy")]
[HttpPost]
public async Task<IHttpActionResult> ReportDiscrepancy(DiscrepancyPost dp)
{
string retval = string.Empty;
dp.lid = AppCore.Validation.StripToGUID(dp.lid);
dp.ProblemTypes = AppCore.Validation.StripToDigitInCSVFromStr(dp.ProblemTypes);
dp.ProblemTypeOther = AppCore.Validation.StripAllCharsForMetaTag(StripHtml(dp.ProblemTypeOther.xToStr()));
var session = HttpContext.Current.Session;
try
{
if (dp.lid.Length == 36 && dp.ProblemTypes.Length > 0 | dp.ProblemTypeOther.Length > 0)
{
if (IsGUID(dp.lid))
{
if (session != null)
{
if (session.SessionID != null)
{
if (session.SessionID.Length > 0)
{
var dr = new Discrepancy
{
lid = dp.lid,
SessionId = session.SessionID,
IPAddress = Ops.Site.GetUserIP(HttpContext.Current),
UserId = "anonymous"
};
if (User != null)
{
if (User.Identity != null)
{
if (User.Identity.IsAuthenticated)
{
dr.UserId = User.Identity.GetUserId();
}
}
}
dr.ProblemTypeIds = dp.ProblemTypes;
dr.ProblemTypeOther = dp.ProblemTypeOther;
dr.idx = await DiscrepancyReportData.InsertDiscrepancyAsync(dr);
if (dr.idx > 0)
{
retval = "success";
}
}
}
}
}
}
}
catch (Exception)
{
//TODO logging
}
return Ok(retval);
}
and the class:
public class DiscrepancyPost
{
public string lid { get; set; }
public string ProblemTypes { get; set; }
public string ProblemTypeOther { get; set; }
}
Works like a charm. So the problem was in the controller signature (fixed that first and got it to work in Postman perfectly), then in the jQuery side.
I have a file uploader component in angular that posts a file and a json object string to a web api controller. unfortunately the values on the server are always null.
Here is the angular method used for upload :
public upload(event: any): void {
const file = event.target.files[0];
const formData = new FormData();
var product = {
productCode: '009',
productName: 'Test Product Name',
clientKey: '616f1d97-6798-459a-957f-6e6476aa3c82',
providerId: 1
};
formData.append('file', file, file.name);
formData.append('body', JSON.stringify(product));
var headers = { headers:{
'Content-Type': 'application/json',
'TimeStamp' : '2022-05-05T16:50:36'
}}
this.http.post('http://localhost:9913/api/internal/testProduct/Add', formData, headers)
.pipe(
map(res => console.log('got response', res),
catchError(error => of(console.error('got error', error)))))
.subscribe(() => console.log('next'), error => console.log(error));
}
And the web api controller method:
[HttpPost("Add")]
public async Task<IActionResult> AddProduct([FromForm] AddProductEntity addProductEntity)
{
ProductRequestContract addNewProductRequestContract = new ProductRequestContract();
var requestObj = JsonConvert.DeserializeObject<ProductRequestContract>(addProductEntity.body);
if (requestObj == null)
{
return base.BadRequest("Not valid product");
}
#region upload file to temp folder
if (addProductEntity.file != null)
{
var uploadFileResult = SaveImage(addProductEntity.file);
if (!uploadFileResult)
{
return BadRequest("Error saving the file.");
}
}
#endregion
return Ok();
}
the parameter AddProductEntity addProductEntity are always null, why web api model binder is unable to match form data sent in the request?
Note: AddProductEntity is a simple DTO class has 2 props which matches the form data values:
public class AddProductEntity
{
public IFormFile file { get; set; }
public string body { get; set; }
}
I have function in a C# controller file that makes an API call to fetch some placeholder data that is hard coded locally.
[Route("api/ReportingCenter/GetReportList")]
[HttpGet]
public class ReportController : ApiController
{
public async Task<IHttpActionResult> GetReportList(CancellationToken cancellationToken)
{
using (var source = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken,
HttpContext.Current.Response.ClientDisconnectedToken))
{
var userId = 11;
var projId = 22;
using (var reportDb = new ReportDb(userId, projId))
{
var list = reportDb.GetReports(userId, projId);
return Json(list);
}
}
}
}
public class ReportDb : PortalDb
{
public List<ReportInfo> GetReports(int userid, int projectid)
{
var ReportList = new List<ReportInfo>();
ReportList.Add(new ReportInfo(1,"file1.pdf", 11, "a file with data in it", 11112222,"pdf", 1, "22"));
}
}
I coded a button and a function with an aspx file that should make an axios call to run the GetReportList function from the controller. When the button is click, nothing happens. Any suggestions on how to make it so that the API call is made when the button is clicked in the aspx file?
<asp:Content ID="Content5" ContentPlaceHolderID="primaryContent" runat="server">
<div id="reporting-app-container">
<input type="button" value="ReportInfo" class="btn btn-sm btn primary"
onclick="getReportInfo"/>
</div>
<script src="/ReportsModule/app/dist/reporting-app-bundle.js"></script>
<script type="text/javascript">
$(document).ready(function () {
window.ReportingApp();
});
async function getReportInfo(id) {
console.log("pre-axios call")
const response = await axios.get("api/ReportingCenter/GetReportList")
.then(function (response) {
// handle success
console.log(response);
})
.catch(function (error) {
// handle error
console.log(error);
});
}
</script>
</asp:Content>
.then and .catch are methods of a Promise, which is what you'd get if you called axios.get without awaiting it. A Promise represents an action which you've started in the background, which may or may not have finished yet, and which can call callback functions supplied by you if/when it completes successfully or throws an error. Or at least that's a very short description of what it is.
To pass your callback functions to the Promise returned by axios.get, remove the async and await, a bit like this:
function getReportInfo(id) {
axios.get('api/ReportingCenter/GetReportList')
.then(function (response) {
// handle success
console.log(response);
})
.catch(function (error) {
// handle error
console.log(error);
})
.then(function () {
// always executed
});
}
If you want response to be the actual response from the API, rather than a Promise to give you the actual response once the web request has completed, then you can await the Promise, a bit like this:
async function getReportInfo(id) {
try {
const response = await axios.get('api/ReportingCenter/GetReportList');
console.log(response);
} catch (error) {
console.error(error);
}
}
A note of caution if you choose the second option - are you sure all your users' browsers support ECMAScript 2017? If not then the await / async approach probably isn't a good idea and you should stick with the first option.
Code snippets are adapted from this bit of the axios API documentation
Moved the javascript function that handles the axios call into reporting-app.js. Vue is being used on top of javascript. The .aspx file and the Vue app are connected by the "el" key, with the value set to the html id: '#reporting-app-container".
.aspx file
<div>HELLO WORLD!</div>
<div id="reporting-app-container">
<input type="button" value="ReportInfo" class="btn btn-sm btn primary" #click="getReportInfo" />
</div>
<script src="/ReportsModule/app/dist/reporting-app-bundle.js"></script>
.js file
import Vue from 'vue';
window.ReportingApp = function () {
new Vue({
el: '#reporting-app-container',
//store: FilesStore,
data: {},
components: {
'reporting-component': reporting_component,
'list-reports-component': list_report_component
},
methods: {
getReportInfo: function (id) {
console.log("pre-axios call");
axios.get("/api/ReportingCenter/GetReportList")
.then(function (response) {
// handle success
console.log(response);
})
.catch(function (error) {
// handle error
console.log(error);
});
}
}
});
}
The javascript and the C# Controller file are linked by the axios method within the methods section of the Vue app.
Controller.cs
using ###.###.###.api.Models;
namespace ###.###.ReportsModule.api
{
public class ReportController : ApiController
{
protected readonly IPortalStateService PortalState;
public ReportController(IPortalStateService portalStateService)
{
PortalState = portalStateService;
}
[Route("api/ReportingCenter/GetReportList")]
[HttpGet]
public IHttpActionResult GetReportList(CancellationToken cancellationToken)
{
using (var source = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken,
HttpContext.Current.Response.ClientDisconnectedToken))
{
var user = PortalState.GetUserId();
if (user == null)
{
return BadRequest("User is not allowed");
}
var userId = 11;
var projId = 22;
try
{
using (var reportDb = new ReportDb(userId, projId))
{
var list = reportDb.GetReports(userId, projId);
return Json(list);
}
}
catch (Exception e)
{
return Json(e);
}
}
}
}
public class ReportDb : PortalDb
{
public List<ReportInfo> GetReports(int userid, int projectid)
{
var ReportList = new List<ReportInfo>();
ReportList.Add(new ReportInfo(1, "file1.pdf", 11, "a file with data in it",
11112222, "pdf", 1, "22"));
return ReportList;
}
}
}
GetReports function uses the model from ReportInfo.cs that defines the field values and the constructor for the data.
ReportInfo.cs Model
namespace ###.###.reportsmodule.api.Models
{
public class ReportInfo
{
public long Id { get; set; }
public string Name { get; set; }
public int Ordinal { get; set; }
public string Description { get; set; }
public int ProjectId { get; set; }
public string SourceType { get; set; }
public int MinPermissionLevel { get; set; }
public string SourceId { get; set; }
public ReportInfo(long id, string name, int ordinal, string description,
int projectId, string sourceType, int minPermissionLevel, string sourceId)
{
Id = id;
Name = name;
Ordinal = ordinal;
Description = description;
ProjectId = projectId;
SourceType = sourceType;
MinPermissionLevel = minPermissionLevel;
SourceId = sourceId;
}
}
}
The javascript side of things looks like this:
var self = this;
self.services = ko.observableArray([]);
self.saveServices = function () {
if (self.services().length > 0) {
var obj = JSON.stringify({ services: self.services() });
$.ajax({
contentType: 'application/json; charset=utf-8',
dataType: 'json',
type: "POST",
url: '/Business/SaveServices',
data: obj,
success: function (data) {
$("#saveModal").modal('show');
if (!data) {
$("#saveDetails").text('Processing error, please try again or email us.');
return;
} else if (data === "saved") {
$("#saveDetails").text('Saved changes.');
setTimeout(function () {
$('#saveModal').modal('hide');
}, 2500);
} else {
$("#saveDetails").text(data);
}
},
error: function (error) {
$("#saveModal").modal('show');
$("#saveDetails").text('Processing error, please try again or email us.');
}
});
}
}
Then my controller looks like this:
[HttpPost]
public async Task<JsonResult> SaveServices([FromBody]SaveServicesRequest saveServicesRequest)
{
try
{
if (User.Identity.IsAuthenticated)
{
return Json("saved");
}
return Json("User is not authenticated.");
}
catch (Exception ex)
{
logger.Error(ex + ". Users Email address: " + User.Identity.Name);
return Json("Processing error, please try again or email us.");
}
}
public class SaveServicesRequest
{
public List<services> services { get; } = new List<services>();
}
public class services
{
public string service { get; set; }
}
Am hitting the controller but the array of items I'm expecting is not returned (saveServicesRequest is empty), I cannot seem to pass an array in any form. Anyone see what am doing wrong? The data/json is being sent client side, think the problem might lie in how am building the json and receiving it.
The problem is that you're initializing services to a new list in SaveServicesRequest. The properties should also have public getters and setters for model binding.
Try updating your class to:
public class SaveServicesRequest
{
public List<services> services { get; set; }
}
I am working on web api project wherein I have to write method to delete an account.
I have created a controller called RemoveAccount where I have written my Delete account method.
Following is the code.
public RemoveAccountRes Delete(RemoveAccountModel objRemove, string function = "")
{
RemoveAccountRes objModel = new RemoveAccountRes();
if (ModelState.IsValid)
{
User user = null;
try
{
user = UserHelper.GetLoggedInUser(Convert.ToString(user.UserGuid));
if (user == null || objRemove.User == null)
{
objModel.ErrorMessage = "User Not Valid";
objModel.ErrorCode = 403;
objModel.Success = false;
return objModel;
}
if (function == "delete")
{
UserRepository.Delete(objRemove.User.UserId);
UserHelper.LogOutUser();
objModel.ErrorMessage = "Account is Deleted";
objModel.ErrorCode = 200;
objModel.UserGuid = Convert.ToString(user.UserGuid);
objModel.Success = true;
}
}
catch (Exception ex)
{
objModel.ErrorMessage = "Unidentified Error";
objModel.ErrorCode = 500;
objModel.UserGuid = Convert.ToString(user.UserGuid);
}
return objModel;
}
else
{
objModel.ErrorMessage = "Invalid/Incomplete requests";
objModel.ErrorCode = 400;
return objModel;
}
}
And the model for the above method is:
public class RemoveAccountModel
{
public User User { get; set; }
}
public class RemoveAccountRes
{
public int ErrorCode { get; set; }
public string ErrorMessage { get; set; }
public int UserId { get; set; }
public string UserGuid { get; set; }
public bool Success { get; set; }
}
I have created a separate Test project to test this api method.
Following is the ajax call which gets invoked on click of Delete button:
$(document).ready(function () {
$("#btnDelete").click(function () {
alert("Hello");
var request = $.ajax({
url: "http://localhost:13979/api/RemoveAccount?function='delete'",
type: "DELETE",
dataType: "json"
});
request.done(function (msg) {
alert("Request success: " + msg);
$("#divResponse").empty().append("<span>" + JSON.stringify(msg) + "</span>");
//console.log(JSON.stringify(msg));
});
request.fail(function (jqXHR, textStatus) {
alert("Request failed: " + textStatus);
$("#divResponse").empty().append("<span>" + JSON.stringify(textStatus) + "</span>");
//console.log(JSON.stringify(textStatus));
});
});
});
When I tried to debug, I found that its not calling that URL and am getting "Internal Server Error" only for this method, rest all are working fine.
Where am I going wrong?
you already have the parameter function=delete does it really matter if the type of HTTP request is DELETE, I would recommend changing it to POST
from jQuery docs:
The type of request to make ("POST" or "GET"), default is "GET". Note: Other HTTP request methods, such as PUT and DELETE, can also be used here, but they are not supported by all browsers.
another thing ... Do you have this mapped properly?
http://localhost:13979/api/RemoveAccount is calling a function called Delete - which is fine if you handle that in your routing code.