I am trying to upload image from my mobile to WCF server using phonegap file transfer API.
Following is my code. But I am not able to upload the image to server. Please give some suggestion.
Test.html
<body >
<div data-role="view" id="uploadView" data-reload="true">
<div data-role="content">
<button onclick="getphoto();">get a Photo</button>
<img src="" id="myimg" style="border:1px solid #0f0;height:200px;width:200px;" />
</div>
</div>
<script>
function getphoto() {
navigator.camera.getPicture(
uploadPhoto,
function(message) {
alert('get picture failed');
},
{
quality: 10,
destinationType:navigator.camera.DestinationType.FILE_URI,
sourceType:navigator.camera.PictureSourceType.PHOTOLIBRARY
}
);
}
function uploadPhoto(imageURI) {
document.getElementById("myimg").src = imageURI;
var options = new FileUploadOptions();
options.chunkedMode = false;
options.fileKey = "recFile";
var imagefilename = imageURI;
options.fileName = imagefilename;
options.mimeType = "image/jpeg";
var ft = new FileTransfer();
alert(imagefilename);
ft.upload(imageURI, "http://myserver/MyAppService.svc/SaveImage", win, fail, options);
}
function win(r) {
//console.log("Code = " + r.responseCode);
//console.log("Response = " + r.response);
alert("Sent = " + r.bytesSent);
}
function fail(error) {
switch (error.code) {
case FileTransferError.FILE_NOT_FOUND_ERR:
alert("Photo file not found");
break;
case FileTransferError.INVALID_URL_ERR:
alert("Bad Photo URL");
break;
case FileTransferError.CONNECTION_ERR:
alert("Connection error");
break;
}
alert("An error has occurred: Code = " + error.code);
}
</script>
</body>
Here is my WCF service.
IMyService.cs
namespace MyAppService
{
[ServiceContract]
public interface IMyService
{
[OperationContract]
[WebInvoke(BodyStyle = WebMessageBodyStyle.Wrapped, Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, UriTemplate = "SaveImage")]
string SaveImage();
}
}
MyService.svc.cs
namespace MyAppService
{
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class MyService : IMyService
{
public string SaveImage()
{
HttpPostedFile file = HttpContext.Current.Request.Files["recFile"];
if (file == null)
return null;
string targetFilePath = AppDomain.CurrentDomain.BaseDirectory + #"Images\Tree\\" + file.FileName;
file.SaveAs(targetFilePath);
return file.FileName.ToString();
}
}
}
Thanks
Thanks for the help.. I found the issue.. When I set aspNetCompatibilityEnabled="true" in web.config file of WCF service, I got the HttpPostedFile.
should try this
<configuration>
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
</system.serviceModel>
</configuration>
or
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
Thanks..
Related
I'm facing an issue that my upload time for an image or PDF of 40+MB is more than 2.5 minutes (20+ seconds of which are just routing the request from the frontend to the backend but I'm worried more about the sql query slowlyness). I pasted some code snippets below. I also don't get an upload percentage indicator and the fetched bytes don't open as an image either in html using <img src="data:image/jpeg;base64,#Convert.ToBase64String(Model.Image)" /> or using Win10 Photos app.
I'm looking first of all for an optimization on how can I write an image or pdf file faster into the database?
Index.cshtml
using (Html.BeginForm("Index", "File", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
#Html.TextBoxFor(m => m.File, new { #type = "file", #accept = "image/jpeg,image/gif,image/png,application/pdf" })
<input type="submit" value="Upload" />
#Html.ValidationSummary()
}
Controller
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Index(Model model)
{
var file = model.File;
if (file != null)
{
if (file.ContentLength == 0)
{
ModelState.AddModelError(String.Empty, "File cannot be empty.");
}
const int maxFileSizeMB = 50;
if (file.ContentLength > maxFileSizeMB * 1024 * 1024)
{
ModelState.AddModelError(String.Empty, $"File cannot be bigger than {maxFileSizeMB} megabytes.");
}
//Check for content-type, file extension, and first bytes if it's indeed a valid image.
if (!file.IsImageOrPdfFile())
{
ModelState.AddModelError(String.Empty, "File is not a valid image.");
}
if (ModelState.IsValid)
{
var fileName = file.FileName;
var fileBytes = new byte[file.ContentLength];
file.InputStream.Read(fileBytes, 0, file.ContentLength);
await _api.SaveFile(fileName, fileBytes);
//success!
return Redirect(Url.Action("Index"));
}
}
//View with errors
return View(model);
}
API method
public async Task SaveFile(string fileName, byte[] fileBytes)
{
var client = CreateRestClient();
client.Timeout = 300000;
var request = CreateJsonPostRequest("File", new SaveFileRequest
{
FileName = fileName,
FileBytes = fileBytes
});
var response = await client.ExecuteAsync(request);
if (response.StatusCode != HttpStatusCode.NoContent && response.StatusCode != HttpStatusCode.OK)
{
throw new Exception(response.Content);
}
}
Backend API
[RoutePrefix("api/File")]
public class FileController : ApiController
{
private readonly IFileActionsRepository _fileActions;
public FileController(IFileActionsRepository fileActions)
{
_fileActions = fileActions;
}
[Authorize]
[HttpPost, Route("")]
public async Task Post([FromBody] SaveFileRequest request)
{
if (request == null) throw new ArgumentNullException(nameof(request));
if (request.FileBytes.Length == 0) throw new ArgumentException("request.FileBytes");
_fileActions.SaveFile(request.FileName, request.FileBytes);
}
}
FileActionsRepository.cs
public class FileActionsRepository : IFileActionsRepository
{
private readonly IDataContext _dataContext;
public FileActionsRepository(IDataContext dataContext)
{
_dataContext = dataContext;
//Get the ObjectContext related to this DbContext
var objectContext = (_dataContext.Context as IObjectContextAdapter).ObjectContext;
//Sets the command timeout for all the commands (since it's too slow)
objectContext.CommandTimeout = 300;
}
public void SaveFile(string fileName, byte[] fileBytes)
{
//Using sql query (with update .write() block) since it's faster than entity framework linq-2-entities.
_dataContext.Context.Database.ExecuteSqlCommand(
"if exists (select FileName from Files with (updlock,serializable) where FileName = #FileName)"
+ " update Files set FileBytes .write(#FileBytes, 0, null), SysModified = GETDATE()"
+ " where FileName = #FileName"
+ " else"
+ " insert into Files (FileName, FileBytes, SysCreated)"
+ " values (#FileName, CONVERT(varbinary, '0x00'), GETDATE())"
+ " update Files set FileBytes .write(#FileBytes, 0, null)"
+ " where FileName = #FileName",
new SqlParameter("#FileName", fileName),
new SqlParameter("#FileBytes", fileBytes));
}
}
I am Facing the problems while Uploading the files to the server through API
When I try to upload the Image size 8 Mb to server From Postman. For the first time, I try to upload it shows "The page was not displayed because the request entity is too large". But the strange thing is "When I upload the small size image like 300 Kb it uploads and tries to upload the next image like 8 MB it uploads and After 3 or 5 minutes im getting same error.
My Upload code
[Route("api/[controller]")]
public class UploadImageController : Controller
{
private IHostingEnvironment _environment;
public UploadImageController(IHostingEnvironment environment)
{
_environment = environment;
}
internal void IsExists(string filepath)
{
if (!Directory.Exists(_environment.WebRootPath + "\\media"))
{
Directory.CreateDirectory(_environment.WebRootPath + "\\media");
}
if (!Directory.Exists(_environment.WebRootPath + "\\media\\" + filepath))
{
Directory.CreateDirectory(_environment.WebRootPath + "\\media\\" + filepath);
}
}
internal string GetNewFileName(string filenamestart, string fullname)
{
Char delimiter = '.';
string fileExtension;
string strFileName = string.Empty;
strFileName = DateTime.Now.ToString().
Replace(" ", string.Empty).
Replace("/", "-").Replace(":", "-");
fileExtension = fullname.Split(delimiter).Last();
Random ran = new Random();
strFileName = $"{ filenamestart}_{ran.Next(0, 100)}_{strFileName}.{fileExtension}";
return strFileName;
}
private bool IsValidExtension(IFormFile filename)
{
bool isValid = false;
Char delimiter = '.';
string fileExtension;
string[] imgTypes = new string[] { "png", "jpg", "gif", "jpeg" };
fileExtension = filename.FileName.Split(delimiter).Last();
// fileExtension = substrings[substrings.Length - 1].ToString();
int fileType = 0;
if (imgTypes.Contains(fileExtension.ToLower()))
{
fileType = 1;
}
switch (fileType)
{
case 1:
if (imgTypes.Contains(fileExtension.ToLower()))
{
isValid = true;
}
break;
default:
isValid = false;
break;
}
return isValid;
}
[HttpPost]
// GET: api/<controller>
[Route("UploadapiImage")]
[RequestFormLimits(MultipartBodyLengthLimit = 209715200)]
[RequestSizeLimit(209715200)]
public async Task<IActionResult> UploadapiImage()
{
try
{
var Files=Request.Form.Files;
if (Files.Count == 0)
{
return Ok(Json(""));
}
IsExists("Mobile");
//var file = Convert.FromBase64String(objUploadModel.File);
string uploads = _environment.WebRootPath + "\\media\\Mobile";
int i = 0;
string[] strFileNames = new string[Files.Count];
foreach (var file in Files)
{
if (IsValidExtension(file))
{
string imagename = GetNewFileName("Mobile", Files[i].FileName);
string fullpath = uploads + "\\" + imagename;
using (FileStream fileStream = System.IO.File.Create(fullpath))
{
await Task.Run(() => Files[i].CopyToAsync(fileStream));
fileStream.Flush();
fileStream.Close();
}
strFileNames[i] = "\\media\\Mobile\\" + imagename;
}
else
{
strFileNames[i] = "";
}
i++;
}
return Ok(strFileNames);
}
catch (Exception ex)
{
return Ok("Exception from Api");
}
}
}
In Startup.cs
services.Configure<FormOptions>(x =>
{
x.ValueLengthLimit = int.MaxValue;
x.MultipartHeadersLengthLimit = int.MaxValue;
x.MultipartBodyLengthLimit = int.MaxValue;
});
In Web Config.
<requestFiltering>
<requestLimits maxAllowedContentLength="1073741824" />
</requestFiltering>
I try to keep logs in server and IIS I didn't find any information.
In IIS logs it writes
#Software: Microsoft Internet Information Services 10.0
Version: 1.0
Date: 2019-11-10 05:59:05
Fields: date time s-ip cs-method cs-uri-stem cs-uri-query s-port cs-username c-ip cs(User-Agent) cs(Referer) sc-status sc-substatus sc-win32-status time-taken
2019-11-10 05:59:05 10.0.0.4 POST /api/UploadImage/UploadapiImage - 443 - 27.34.104.230 Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/78.0.3904.97+Safari/537.36 - 413 0 0 1636
My IIS version in sever is show on Image.
Why I am getting the problem ("The page was not displayed because the request entity is too large")
in my website, SSL Setting is Accept
I just set it to Ignore then it works.
I'm trying to send list of objects from MVC to WEBAPI using below methods. API is able to able receive the list from controller but, value of each item in the list is either empty/null on API side.
Can anyone please help me to fix this?
Controller Method:
private List<FCM.Models.Facility> GetFacilityDetails()
{
var url = "http://localhost:64664/";
var facilies = new List<Facility>();
facilies.Add( new Facility{ FCLT_ID = 100, FCLT_NM = "Facility 100" });
facilies.Add( new Facility{ FCLT_ID = 200, FCLT_NM = "Facility 200" });
facilies.Add( new Facility{ FCLT_ID = 300, FCLT_NM = "Facility 300" });
var json = JsonConvert.SerializeObject(facilies);
var _client = new RestClient(url);
var request = new RestRequest("api/facility/details", Method.GET) { RequestFormat = DataFormat.Json };
facilies.ForEach(fclt =>
request.AddParameter("facilites", fclt, ParameterType.GetOrPost));
var response = _client.Execute<List<FCM.Models.Facility>>(request);
if (response.Data == null)
{
throw new Exception(response.ErrorMessage);
}
return response.Data;
}
WebAPI method:
[Route("api/facility/details")]
public IEnumerable<Facility> GetFullAddress([FromUri] IEnumerable<Facility> facilities)
{
return null;
}
Like the comment suggested you maybe want to issue a POST request instead, but if you would like to send an array with a GETrequest you could do it like this (with System.Net.Http.HttpClient):
Add a Format method to you Facility class:
public class Facility
{
public int FCLT_ID { get; set; }
public string FCLT_NM { get; set; }
public string Format(int index)
{
return $"[{index}].FCLT_ID={FCLT_ID}&[{index}].FCLT_NM={FCLT_NM}";
}
}
Define a class which can format the array values:
public class FacilityList : List<Facility>
{
public string Format()
{
var builder = new StringBuilder();
for (var i = 0; i < Count; i++)
{
builder.Append(this[i].Format(i));
if(i != Count -1)
{
builder.Append("&");
}
}
return builder.ToString();
}
}
And then issue the request:
var client = new HttpClient()
{
BaseAddress = new Uri("http://localhost:64664/"),
DefaultRequestHeaders = {Accept = {new MediaTypeWithQualityHeaderValue("application/json")}}
};
var facilities = new FacilityList
{
new Facility {FCLT_ID = 100, FCLT_NM = "Facility 100"},
new Facility {FCLT_ID = 200, FCLT_NM = "Facility 200"},
new Facility {FCLT_ID = 300, FCLT_NM = "Facility 300"}
};
var format = facilities.Format();
var response = client.GetAsync("api/facility/details?" + format).GetAwaiter().GetResult();
var result = JsonConvert.DeserializeObject<IEnumerable<Facility>>(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());
This will bind to your controller action:
[Route("api/facility/details")]
public IHttpActionResult Get([FromUri] IEnumerable<Facility> facilities)
{
// Do stuff..
return Ok(facilities);
}
We have an MVC 4 web application where we use the web.config file to handle custom errors.
<system.webServer>
<validation validateIntegratedModeConfiguration="false" />
<httpErrors errorMode="Custom" existingResponse="Replace">
<remove statusCode="403" />
<error statusCode="403" responseMode="ExecuteURL" path="/Error/AccessDenied" />
<remove statusCode="404" />
<error statusCode="404" responseMode="ExecuteURL" path="/Error/NotFound" />
<remove statusCode="500" />
<error statusCode="500" responseMode="ExecuteURL" path="/Error/ApplicationError" />
</httpErrors>
</system.webServer>
All ,of which works as expected.
We are now beginning to implement some new features in this project using AngularJS and Web API. In our Web API controller actions, we are consistently returning a HttpResponseMessage to indicate success/failure of the call. For example:
return Request.CreateResponse(HttpStatusCode.BadRequest, result);
The problem (I think!) we are having is that originally MVC error handling is intercepting the BadRequest result (as is reasonable) so that the HttpResponseMessage result data never gets returned to the calling AngularJS method.
What is the best way to handle errors in this mixed (MVC/Web API) environment so that the Web API HttpResponseMessages are not lost?
Thanks.
I am not sure I have found the best solution, but in the end I removed the httpErrors section from the Web.config and built my own error handler in the Global.asax assisted by the following posts:
StackOverflow
PrideParrot
Global.asax
public void Application_Error(Object sender, EventArgs e)
{
var httpContext = ((MvcApplication) sender).Context;
var currentController = "";
var currentAction = "";
var currentRouteData =
RouteTable.Routes.GetRouteData(new HttpContextWrapper(httpContext));
if (currentRouteData != null)
{
if (
!String.IsNullOrEmpty(
currentRouteData.Values["controller"]?.ToString()))
{
currentController = currentRouteData.Values["controller"].ToString();
}
if (!String.IsNullOrEmpty(currentRouteData.Values["action"]?.ToString()))
{
currentAction = currentRouteData.Values["action"].ToString();
}
}
var ex = Server.GetLastError();
var httpEx = ex as HttpException;
var controller = new ErrorController();
var routeData = new RouteData();
var statusCode = httpEx?.GetHttpCode() ?? 500;
string action;
switch (statusCode)
{
case 400:
action = "BadRequest";
break;
case 403:
action = "AccessDenied";
break;
case 404:
action = "NotFound";
break;
default:
action = "Index";
break;
}
httpContext.ClearError();
httpContext.Response.Clear();
httpContext.Response.StatusCode = statusCode;
httpContext.Response.TrySkipIisCustomErrors = true;
if (statusCode >= 500)
{
Server.Transfer("/Error/ServerError.html");
return;
}
routeData.Values["controller"] = "Error";
routeData.Values["action"] = action;
routeData.Values["statusCode"] = statusCode;
controller.ViewData.Model = new HandleErrorInfo(ex, currentController,
currentAction);
((IController) controller).Execute(
new RequestContext(new HttpContextWrapper(httpContext), routeData));
}
My error controller then looked like this:
[AllowAnonymous]
public sealed class ErrorController
: AblController
{
public ActionResult Index(int statusCode)
{
ViewBag.StatusCode = statusCode;
return View("Error");
}
// HTTP 400 - Bad Request
public ActionResult BadRequest()
{
// Now handled by Global.asax - Application_Error
// Response.StatusCode = (int) HttpStatusCode.BadRequest;
// Response.TrySkipIisCustomErrors = true;
if (Request.IsAjaxRequest())
{
return Json(
new
{
error = new ErrorSummary("Bad Request")
});
}
return View();
}
// HTTP 403 - Access Denied
public ActionResult AccessDenied()
{
// Now handled by Global.asax - Application_Error
// Response.StatusCode = (int) HttpStatusCode.Forbidden;
// Response.TrySkipIisCustomErrors = true;
if (Request.IsAjaxRequest())
{
return Json(
new
{
error = new ErrorSummary("Access Denied")
});
}
return View();
}
// HTTP 404 - Not Found
public ActionResult NotFound()
{
// Now handled by Global.asax - Application_Error
// Response.StatusCode = (int) HttpStatusCode.NotFound;
// Response.TrySkipIisCustomErrors = true;
if (Request.IsAjaxRequest())
{
return Json(
new
{
error = new ErrorSummary("Not Found")
});
}
return View();
}
}
}
I also turned the custom error mode off in the Web.config
<customErrors mode="Off" />
This solution needs more testing, but so far it seems to be performing as expected/as required.
I have a requirement on my web app to save multiple attachments to my SQL server. The attachments can be in any format. Is it possible to create a knockout viewmodel to encode the file type on client side to binary and transferring that binary info to my SQL server?
I currently have this method in my REST service:
[OperationContract, WebInvoke(ResponseFormat = WebMessageFormat.Json)]
public string PostAttachments (string FileName)
{
try
{
var MyAttachment = new Binary(File.ReadAllBytes(FileName));
return "Attachments Created";
}
catch (Exception ex)
{
return DefaultError + ex;
}
}
I know this is incorrect but I am really not sure how to do this file upload. I believe I am over complicating it. Any Assistance would be greatly appreciated.
You will have to transfer the buffer to the server not just the filename, since the file exists on the client.
I used the HTML5 FileReader in my last KO project
First a custom binding to read the file from the DOM element
(function () {
ko.bindingHandlers.file = {
init: function (element, valueAccessor, allBindingsAccessor) {
ko.utils.registerEventHandler(element, "change", function () {
writeValueToProperty(valueAccessor(), allBindingsAccessor, "file", element.files[0]);
});
},
update: function (element, valueAccessor) {
if (ko.utils.unwrapObservable(valueAccessor()) == null) {
element.value = "";
}
}
};
var writeValueToProperty = function (property, allBindingsAccessor, key, value, checkIfDifferent) {
if (!property || !ko.isObservable(property)) {
var propWriters = allBindingsAccessor()['_ko_property_writers'];
if (propWriters && propWriters[key])
propWriters[key](value);
} else if (ko.isWriteableObservable(property) && (!checkIfDifferent || property.peek() !== value)) {
property(value);
}
};
} ());
FileReader usage (code specific for how I used it in my app). Check the FileReader part only.
Note if you have large files you need to stream instead
processFiles: function (viewModel, callback) {
var files = [];
ko.utils.arrayForEach(viewModel.files, function (file) {
if (file.fileUpload() == null) return;
var count = Enumerable.From(viewModel.files)
.Where(function(f) { return f.fileUpload() != null; })
.Count();
var reader = new FileReader();
reader.onload = function (e) {
var base64 = e.target.result.substr(e.target.result.indexOf(",") + 1);
files.push({ Type: file.type, Data: base64, Filename: file.fileUpload().name });
if (files.length == count) {
callback(files);
}
};
reader.readAsDataURL(file.fileUpload());
});
}
Update
Like said this is code for my use case, you will need to change it to fit your purpose
<div data-bind="foreach: files">
<p><label data-bind="text: typeName"></label><input data-bind="file: fileUpload" type="file" /></p>
</div>
Controller (I would use WebApi if I did this today)
public JsonResult UploadFiles(IEnumerable<FileUploadViewModel> uploads)
{
fileRepository.SaveUploadedFiles(Mapper.Map<IEnumerable<FileUploadViewModel>, IEnumerable <FileUpload>> (uploads));
return string.Empty.AsJson();
}
FileUploadViewModel
public class FileUploadViewModel
{
public FileType Type { get; set; }
public string Filename { get; set; }
public string Data { get; set; }
}
Automapper config
Mapper.CreateMap<FileUploadViewModel, FileUpload>()
.ForMember(to => to.Buffer, opt => opt.MapFrom(from => Convert.FromBase64String(from.Data)));