MVC Jsonresult from memorystream json file - c#

Thanks in advance,
I need an MVC controller (=GetFreqSuggestions) that returns a JsonResult to feed the prefetch of a Typeahead javascript function.
$(function() {
var bestPictures = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
prefetch:{
url: '../Reports/GetFreqSuggestions',
ttl:0
},
remote: '../Reports/ToDo?q=%QUERY'
});
bestPictures.initialize();
$('#remote .typeahead').typeahead({
hint: true,
highlight: true,
},
{
name: 'best-pictures',
displayKey: 'value',
source: bestPictures.ttAdapter()
});
});
The Json file is obtained from a azure blob storage as memorystream :
public async Task<bool> DownloadStreamFromBlobAsync(MemoryStream memStr, string blobContainerName, string blobName, ILogger log)
{
Stopwatch timespan = Stopwatch.StartNew();
try
{
CloudBlobContainer container = GetCloudBlob(blobContainerName);
if (container != null & memStr!=null)
{
CloudBlockBlob blockBlob = container.GetBlockBlobReference(blobName);
await blockBlob.DownloadToStreamAsync(memStr);
}
}
catch (Exception e)
{
log.Error(.....);
return false;
}
timespan.Stop();
log.TraceApi(.......);
return true;
}
In the controller I would like to convert the memorystream containing a pervious uploaded json file to a jsonresult.
public async Task<JsonResult> GetFreqSuggestions()
{
MemoryStream mem = await _suggestoinProvider.DownloadFrequentSuggestionsAsync();
if(mem != null)
{
return ????
}
return null;
}
All suggestions are welcome.
(I have tested with return Json(data, JsonRequestBehavior.AllowGet); that works perfectly when data was not a stream)
I do not prefer to save a Json file first on a server.

I think that either .ToArray() or .ReadToEnd() will do the trick here:
return Json(mem.ToArray(), JsonRequestBehavior.AllowGet);
or
return Json(mem.ReadToEnd(), JsonRequestBehavior.AllowGet);

Related

IFormFile copy to memorystream ObjectDisposedException

I'm currently using tinyMCE to create some news post for a website.
I need to be able to upload images, however i've hit a stopblock.
When I hit my controller I get an
System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'FileBufferingReadStream'.
at Microsoft.AspNetCore.WebUtilities.FileBufferingReadStream.ThrowIfDisposed()
at Microsoft.AspNetCore.WebUtilities.FileBufferingReadStream.set_Position(Int64 value)
at Microsoft.AspNetCore.Http.Internal.ReferenceReadStream..ctor(Stream inner, Int64 offset, Int64 length)
at Microsoft.AspNetCore.Http.Internal.FormFile.OpenReadStream()
at Microsoft.AspNetCore.Http.Internal.FormFile.CopyToAsync(Stream target, CancellationToken cancellationToken)
at HardwareOnlineDk.Web.Areas.Admin.Controllers.ImageController.Upload(IFormFile inputFile) in D:\Kode\HardwareOnlineRider\HOL\SourceCode\Main\Web\Areas\Admin\Controllers\ImageController.cs:line 139
My code looks like this:
[HttpPost, ActionName("Upload")]
public async Task<IActionResult> Upload(IFormFile inputFile)
{
try
{
var filesCount = Request.Form.Files.Count;
if (filesCount == 0)
return BadRequest("Ingen fil fundet");
// Get HTTP posted file based on the fieldname.
var file = Request.Form.Files.GetFile("file");
if (file == null)
return BadRequest("Fejlkonfiguration: Filnavn ikke korrekt");
// Check if the file is valid.
if (!Check(file.FileName, file.ContentType))
return BadRequest("Fil ikke gyldig");
var memoryStream = new MemoryStream();
await file.CopyToAsync(memoryStream);
var medie = new Medie
{
Name = file.FileName.Trim('\"'),
ParentId = _imageService.TempFolderGuid,
ContentLength = file.Length,
Content = memoryStream.ToArray()
};
try
{
var imageId = await _imageService.Medier_InsertMedie(medie);
//TODO Her skal vi gemme ImageId i Session
return Json(new
{
location = $"/api/media/{imageId.Id}.jpg"
});
}
catch
{
return BadRequest("Kunne ikke gemme billede");
}
}
catch
{
return StatusCode(500);
}
}
And the Check Method if needed
private static bool Check(string filePath, string mimeType)
{
return AllowedImageExts.Contains(GetFileExtension(filePath)) &&
AllowedImageMimetypes.Contains(mimeType.ToLower());
}
The code fails when i'm doing:
await file.CopyToAsync(memoryStream)
Can anyone help me here. I'm lost.
UPDATE 1
I just tried to fix it with the suggested answer, so my code now looks like this:
[HttpPost, ActionName("Upload")]
public async Task<IActionResult> Upload([FromForm]IFormFile file)
{
try
{
if (file == null)
return BadRequest("Fejlkonfiguration: Filnavn ikke korrekt");
var memoryStream = new MemoryStream();
await file.CopyToAsync(memoryStream);
var filesCount = Request.Form.Files.Count;
if (!Request.ContentType.StartsWith(MultipartContentType))
return BadRequest("Contenttype er ikke korrekt");
if (filesCount == 0)
return BadRequest("Ingen fil fundet");
// Get HTTP posted file based on the fieldname.
// Check if the file is valid.
if (!Check(file.FileName, file.ContentType))
return BadRequest("Fil ikke gyldig");
var medie = new Medie
{
Name = file.FileName.Trim('\"'),
ParentId = _imageService.TempFolderGuid,
ContentLength = file.Length,
Content = memoryStream.ToArray()
};
try
{
var imageId = await _imageService.Medier_InsertMedie(medie);
//TODO Her skal vi gemme ImageId i Session
return Json(new
{
location = $"/api/media/{imageId.Id}.jpg"
});
}
catch
{
return BadRequest("Kunne ikke gemme billede");
}
}
catch
{
return StatusCode(500);
}
}
The input parameter is no longer null, but it still throws the same exception
You indicated that inputFile is always null.
Binding matches form files by name.
Reference Upload files in ASP.NET Core
Based on
// Get HTTP posted file based on the fieldname.
var file = Request.Form.Files.GetFile("file");
the name of the posted field is "file"
rename the action parameter to match and explicitly state when to bind the data using [FromForm]
[HttpPost, ActionName("Upload")]
public async Task<IActionResult> Upload([FromForm]IFormFile file) {
try {
if (file == null)
return BadRequest("Fejlkonfiguration: Filnavn ikke korrekt");
// Check if the file is valid.
if (!Check(file.Name, file.ContentType))
return BadRequest("Fil ikke gyldig");
var medie = new Medie {
Name = file.Name.Trim('\"'),
ParentId = _imageService.TempFolderGuid
};
var fileStream = file.OpenStreamRead();
using (var memoryStream = new MemoryStream()) {
await fileStream.CopyToAsync(memoryStream);
medie.Content = memoryStream.ToArray();
medie.ContentLength = memoryStream.Length,
}
try {
var imageId = await _imageService.Medier_InsertMedie(medie);
//TODO Her skal vi gemme ImageId i Session
return Json(new {
location = $"/api/media/{imageId.Id}.jpg"
});
} catch {
return BadRequest("Kunne ikke gemme billede");
}
} catch {
return StatusCode(500);
}
}
I feel like I should post how I solved it
The issue was that I have a log in middleware, that reads the httpcontext.
Using the answer found here: How can I read http request body in netcore 3 more than once?
solved the issue for me.
you can pass additional parameters with a class with a IFormFile element. pass the byte data to a stored procure as a byte[] array. where the FileData field is a varbinary parameter in the stored procedure.
public class FormData
{
public string FileName { get; set; }
public string FileDescription { get; set; }
public IFormFile file { get; set; }
}
controller end point
public async Task<IActionResult> UploadDocument([FromForm] FormData formData)
{
if (formData.file.Length > 0)
{
using (MemoryStream ms = new MemoryStream())
{
await formData.file.CopyToAsync(ms);
data = ms.ToArray();
await _repository.spProcedure(FileData: data);
}
}

return file application.pdf to browser via asp net mvc controller shows code instead of file

I have a pdf file in a database that I loaded in the controller and directly want to return via return File
return File(document.Data, document.ContentType);
Data is the bytearray with ~76000 bytes
ContentType is the application/pdf
When I want to view the result in webbrowser (either FF or Chrome) I get to see the pdf code instead of the pdf.
%PDF-1.4 %���� 1 0 obj <>stream x��� and so on
Would appreciate any help because it must be so simple, but I can't find it.
The return is placed in ActionResult Index and I need it to be loaded right at click on page (no new page with _blank)
It is the right data, because when I use f12 in chrome and click on network and the data I get to view the pdf as a whole
Edit1:
[HttpGet]
public ActionResult Index(int Id)
{
InitMvcApplicationMenu();
...
var document = WebApi.LoadDocument(DocumentGuid.Value);
var byteArray = document.Data;
if (byteArray == null)
{
return null;
}
Stream stream = new MemoryStream(byteArray);
if (document == null)
{
return View("NoDocument");
}
Response.AppendHeader("content-disposition", "inline; filename=file.pdf");
return File(stream, document.ContentType, "document.pdf");
}
This way I get an error no file was found. When I use it the way before with
return File(document.Data, document.ContentType);
I get the bytearray as view instead of a pdf, but the file is found
Edit 2:
public ActionResult Index(int Id)
{
InitMvcApplicationMenu();
var entity = WebApi.LoadItem(Id);
var DocumentGuid = entity.ReportDocumentGUID;
if (DocumentGuid == Guid.Empty)
{
return View("NoDocument");
}
var document = WebApi.LoadItem(DocumentGuid.Value);
if (document == null)
{
return View("NoStatusReportDocument");
}
var cd = new ContentDisposition
{
FileName = document.Name,
Inline = true
};
Response.Headers.Add("Content-Disposition", cd.ToString());
return File(document.Data, document.ContentType);
}
I have a Wrapper with multiple registertabs and want to show the pdf inside the tab when the document tab is selected.
This happens here:
my.onHashChanged = function (e) {
var feature = jHash.val('feature');
my.loadTab(feature);
}
my.loadTab = function (feature) {
if (feature) {
$("#tabstrip-content").html("Loading...");
applicationUIModule.loadPartialView(feature, "Index", { Id: $("#Id").val()}
, function (data) {
}
, null
, $("#tabstrip-content")
);
}
}
my.onTabSelect = function (e) {
var feature = $(e.item).data("feature");
applicationUIModule.updateHash("feature", feature);
}
This is what you have to do:
[HttpGet]
public IActionResult document(int id)
{
var byteArray = db.instances.Where(c => c.id == id).FirstOrDefault().document;
if (byteArray == null)
{
return null;
}
Stream stream = new MemoryStream(byteArray);
Response.AppendHeader("content-disposition", "inline; filename=file.pdf");
return File(stream, "application/pdf", "document.pdf");
}

Http post .csv file Angular 2 and then reading it on the server .NET

I'm trying to send .csv file from my client app (angular 2) to my web api (ASP.NET), and I have done the following:
Tried to make FormData from my .csv file the following way:
public sendData() {
let formData = new FormData();
formData.append('file', this.file, this.file.name);
this.myService.postMyData(formData, this.name)
.subscribe(data => this.postData = JSON.stringify(data),
error => this.error = error,
() => console.log('Sent'));
}
Created a service on the client app where I'm sending this .csv file from.
postMyData(formData: any, name: string) {
this.s = <string><any>name;
const headers = new Headers();
headers.append('Content-Disposition', 'form-data');
const url: string = 'myUrl?methodName=' + name;
return this.http.post(url, formData, {headers: headers})
.map((res: Response) => res.json());
}
What's the problem now is that I don't know how to get that .csv file on the server. I tried it with the code found below, but I can't get the real content, I can only see the name, content type, length and stuff like that.
[HttpPost("GetMyCsvFile")]
public async Task<IActionResult> GetMyCsvFile(string name) {
var rawMessage = await Request.ReadFormAsync();
var msg = rawMessage.Files[0];
....
}
And then whatever I do with rawMessage, I can't get the content which I could read and do the stuff needed.
Is this possible to do?
You need to get the file and not the file name. Try this code, I'm getting a CSV file from my angular app.
public async Task<bool> GetFileFromAngular(IFormFile file) {
using (var reader = new StreamReader(file.OpenReadStream())) {
var config = new CsvConfiguration(CultureInfo.InvariantCulture) {
HasHeaderRecord = true,
MissingFieldFound = null,
BadDataFound = null,
TrimOptions = TrimOptions.Trim
};
using (var csv = new CsvReader(reader, config)) {
try {
var records = csv.GetRecords<DrugFormulary>().ToList();
var csvProcessor = new CsvProcessor(_dbContext, _configuration);
await csvProcessor.ProcessPlan(records);
} catch (System.Exception ex) {
throw ex;
}
}
}
return true;
}

AngularJS HTTP POST using ng-file

I can't seem to get this working with ng-file upload.
I need to pass in fine in my controller with bridge Id
The error I'm getting is:
"{"Message":"No HTTP resource was found that matches the request URI
'http://localhost/api/BridgeController/'.","MessageDetail":"No type was
found that matches the controller named 'BridgeController'."}"
Can't figure out why it can't find my method. Any ideas?
Here is my controller code that will get moved into a service
$scope.uploadFile = function (file) {
console.log("hitting file upload", $scope.selectedBridge);
if (file) {
debugger;
var request = {
method: 'POST',
url: '/api/Bridge/UploadBridgeImage',
data: angular.toJson($scope.selectedBridge.BridgeID),
file: file,
headers: { 'Content-Type': undefined }
};
Upload.upload(request).then(function (response) {
});
}
}
and back end C#
[HttpPost]
[Route("api/Bridge/UploadBridgeImage")]
public IHttpActionResult UploadBridgeImage()
{
try
{
var uploadedFiles = HttpContext.Current.Request.Files;
for (int i = 0; i < uploadedFiles.Count; i++)
{
var fileToSave = uploadedFiles[i];
var fileBytes = _iStremHelper.GetBytes(fileToSave.InputStream, fileToSave.ContentLength);
var file = BridgeFileEntity(fileBytes, fileToSave, 1);
using (_iFileRepository)
{
_iFileRepository.Save(file);
}
}
return Ok();
}
catch (Exception ex)
{
return InternalServerError();
}
}
Edited Code Here.
I was able to hit my break point in that post method in my C# controller. File have all the data I need. Now I need to get "data" some how. Any idea where is it located in context?
In order to get both file stream (in memory) and additional json data in web api controller you can define custom MultipartStreamProvider:
class MultipartFormDataInMemoryStreamProvider : MultipartFormDataRemoteStreamProvider
{
public override RemoteStreamInfo GetRemoteStream(HttpContent parent, HttpContentHeaders headers)
{
return new RemoteStreamInfo(new MemoryStream(), string.Empty, string.Empty);
}
}
Then use it in controller:
public async Task<IHttpActionResult> UploadBridgeImage()
{
var provider = await Request.Content.ReadAsMultipartAsync(new MultipartFormDataInMemoryStreamProvider());
foreach (var httpContent in provider.Contents)
{
if (!string.IsNullOrEmpty(httpContent.Headers.ContentDisposition?.FileName))
{
var byteArray = await httpContent.ReadAsByteArrayAsync();
//do whatever you need with byteArray
}
}
var bridgeId = provider.FormData["bridgeId"];
return Ok();
}
And on client side:
var request = {
url: '/api/Bridge/UploadBridgeImage',
data: {
file:file,
bridgeId:$scope.selectedBridge.BridgeID
}
};
Upload.upload(request).then(function (response) {
});

How to upload image from PhoneGap to Asp.Net Web API?

I have these available APIs:
[HttpPost]
[CorsEnabled]
[ActionName("upstream")]
public DTO.Callback UploadPhoto(Stream data)
{
var m = new Logic.Components.User();
return m.UploadPhoto(data, Common.UserValues().Email);
}
[HttpPost]
[CorsEnabled]
[ActionName("upbyte")]
public DTO.Callback UploadPhoto(byte[] data)
{
var m = new Logic.Components.User();
return m.UploadPhoto(data, Common.UserValues().Email);
}
[HttpPost]
[CorsEnabled]
[ActionName("upfile")]
public DTO.Callback UploadPhoto(HttpPostedFileBase data)
{
var m = new Logic.Components.User();
return m.UploadPhoto(data, Common.UserValues().Email);
}
[HttpPost]
[CorsEnabled]
[ActionName("up")]
public DTO.Callback UploadPhoto(DTO.UserPhoto data)
{
var m = new Logic.Components.User();
return m.UploadPhoto(data, Common.UserValues().Email);
}
UserPhoto class
public class UserPhoto
{
public string Base64 { get; set; }
public byte[] Data { get; set; }
}
In the behind code, I try to convert or get the equivalent byte[] of each request data.
If I would get the correct Image byte, then I'm good to go.
In my PhoneGap application, I have these codes:
A function that opens the camera:
takePicture: function (success, error) {
var s = function (data) {
navigator.camera.cleanup();
(success || angular.noop)(data);
},
e = function (data) {
navigator.camera.cleanup();
(error || angular.noop)(data);
};
navigator.camera.getPicture(s, e,
{
quality: 100,
destinationType: Camera.DestinationType.FILE_URI,
sourceType: Camera.PictureSourceType.CAMERA,
encodingType: Camera.EncodingType.PNG,
correctOrientation: true
}
);
}
My first try is to convert the image to base64 string and use the 'up' API.
It works just fine for low quality not higher than 50. But the image becomes almost unrecognizable. So I set the quality to 100. And then the new problem comes, the phone hangs...
So I tried to use FileTransfer. Here is the code:
fileTransfer: function (filePath, serverUri, mimeType, params, success, error) {
var
u = $coreData.user.getSession()
;
var options = new FileUploadOptions();
options.fileKey = 'file';
options.mimeType = mimeType;
options.params = params;
options.chunkedMode = true;
options.headers = {
Connection: 'close',
Device: m.createDeviceHeader(),
'Authentication-Token': (u && u.SessionKey),
'Content-Type': 'application/json'
};
var ft = new FileTransfer();
ft.upload(filePath, encodeURI(serverUri), success, error, options);
}
Sample usage:
uploadFile: function (path) {
var def = $q.defer();
$coreUtility
.fileTransfer(path, $coreAPI.user.getUrl('upfile'), 'image/png', null,
function (success) {
def.resolve(m.callback(true, UPLOAD_PHOTO_SUCCESS, success));
},
function (error) {
def.reject(m.callback(false, UPLOAD_PHOTO_FAIL, error));
});
return def.promise;
}
But I was not able to upload the file, I always get not supported media type formatter and sometimes null reference exceptions. I'm totally out of idea.
Alright I get it now. For other struggling on the same problem, here is the solution.
[HttpPost]
[CorsEnabled]
[ActionName("upfile")]
public DTO.Callback UploadPhoto()
{
var m = new Logic.Components.User();
return m.UploadPhoto(HttpContext.Current.Request, Common.UserValues().Email);
}
Some logic:
public DTO.Callback UploadPhoto(HttpRequest req, string email)
{
if (req.Files.Count > 0)
{
var file = req.Files[0];
var m = new Logic.Components.User();
return m.UploadPhoto(file.InputStream, email);
}
return new DTO.Callback { Message = "Fail to upload. Make sure that you are uploading an image file." };
}
Some explanation about the solution:
The first part is your API, and the second part is the backend code.
To pass the image stream from PhoneGap to your ASP.Net MVC Web API, you will just have to use HttpContext.Current.Request to get the Stream from Phonegap.

Categories

Resources