Manipulating the received Json Data in Web API Controller - asp.net

I am passing Json Data from Angular JS Controller. The Json Data contains two strings called name attribute and comment attribute and a list of files. The controller code for angular is given below:
app.controller("demoController", function ($scope, $http) {
//1. Used to list all selected files
$scope.files = [];
//2. a simple model that want to pass to Web API along with selected files
$scope.jsonData = {
name: "Sibnz",
comments: "This is a comment"
};
//3. listen for the file selected event which is raised from directive
$scope.$on("seletedFile", function (event, args) {
$scope.$apply(function () {
//add the file object to the scope's files collection
$scope.files.push(args.file);
});
});
//4. Post data and selected files.
$scope.save = function () {
$http({
method: 'POST',
url: "http://localhost:51739/PostFileWithData",
headers: { 'Content-Type': undefined },
transformRequest: function (data) {
var formData = new FormData();
formData.append("model", angular.toJson(data.model));
for (var i = 0; i < data.files.length; i++) {
formData.append("file" + i, data.files[i]);
}
return formData;
},
data: { model: $scope.jsonData, files: $scope.files }
}).
success(function (data, status, headers, config) {
alert("success!");
}).
error(function (data, status, headers, config) {
alert("failed!");
});
};
});
In the Web API, controller I am receiving the JSON data by using the following code:
[HttpPost]
[Route("PostFileWithData")]
public async Task<HttpResponseMessage> Post()
{
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
var root = HttpContext.Current.Server.MapPath("~/App_Data/Uploadfiles");
Directory.CreateDirectory(root);
var provider = new MultipartFormDataStreamProvider(root);
var result = await Request.Content.ReadAsMultipartAsync(provider);
var model = result.FormData["jsonData"];
var g = result.FileData;
if (model == null)
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
//TODO: Do something with the JSON data.
//get the posted files
foreach (var file in result.FileData)
{
//TODO: Do something with uploaded file.
var f = file;
}
return Request.CreateResponse(HttpStatusCode.OK, "success!");
}
When I debug the code, I find that the JSON data is populating the var model and var g variables. I want to extract the name and comment attributes from the Json Data and store them in the Database. And also want to copy the file into /App_Data/Uploadfiles directory and store the file location in the database.

You need to create a model in your Web API and deserialize JSON data to this model, you can use Newtonsoft.Json NuGet package for that
Install-Package Newtonsoft.Json
class DataModel
{
public string name { get; set; }
public string comments { get; set; }
}
In Web API controller
using Newtonsoft.Json;
HttpRequest request = HttpContext.Current.Request;
var model = JsonConvert.DeserializeObject<DataModel>(request.Form["jsonData"]);
// work with JSON data
model.name
model.comments
To work with files
// Get the posted files
if (request.Files.Count > 0)
{
for (int i = 0; i < request.Files.Count; i++)
{
Stream fileStream = request.Files[i].InputStream;
Byte[] fileBytes = new Byte[stampStream.Length];
// Do something with uploaded file
var root = HttpContext.Current.Server.MapPath("~/App_Data/Uploadfiles/");
string fileName = "image.jpg";
File.WriteAllBytes(root + fileName, stampBytes);
// Save only file name to your database
}
}

Related

Download multiple files (50mb) blazor server-side

i can't really find a way to download a 100mb zip file from the server to the client and also show the progress while downloading. So how will this look for a normal api controller i can add to my server-side project? if lets say i have 3 files i want to download at 50mb each.
i have tried using JSInterop like this, but this is not showing the progress of the file download, and how will i do if i want to download 3 seperate files at the same time?
try
{
//converting file into bytes array
var dataBytes = System.IO.File.ReadAllBytes(file);
await JSRuntime.InvokeVoidAsync(
"downloadFromByteArray",
new
{
ByteArray = dataBytes,
FileName = "download.zip",
ContentType = "application/force-download"
});
}
catch (Exception)
{
//throw;
}
JS:
function downloadFromByteArray(options: {
byteArray: string,
fileName: string,
contentType: string
}): void {
// Convert base64 string to numbers array.
const numArray = atob(options.byteArray).split('').map(c => c.charCodeAt(0));
// Convert numbers array to Uint8Array object.
const uint8Array = new Uint8Array(numArray);
// Wrap it by Blob object.
const blob = new Blob([uint8Array], { type: options.contentType });
// Create "object URL" that is linked to the Blob object.
const url = URL.createObjectURL(blob);
// Invoke download helper function that implemented in
// the earlier section of this article.
downloadFromUrl({ url: url, fileName: options.fileName });
// At last, release unused resources.
URL.revokeObjectURL(url);
}
UPDATE:
if im using this code, it will show me the progress of the file. But how can i trigger it from my code? This way does not do it. But typing the url does.
await Http.GetAsync($"Download/Model/{JobId}");
Controller
[HttpGet("download/model/{JobId}")]
public IActionResult DownloadFile([FromRoute] string JobId)
{
if (JobId == null)
{
return BadRequest();
}
var FolderPath = $"xxxx";
var FileName = $"Model_{JobId}.zip";
var filePath = Path.Combine(environment.WebRootPath, FolderPath, FileName);
byte[] fileBytes = System.IO.File.ReadAllBytes(filePath);
return File(fileBytes, "application/force-download", FileName);
}
UPDATE 2!
i have got it download with progress and click with using JSInterop.
public async void DownloadFiles()
{
//download all selectedFiles
foreach (var file in selectedFiles)
{
//download these files
await JSRuntime.InvokeAsync<object>("open", $"Download/Model/{JobId}/{file.Name}", "_blank");
}
}
Now the only problem left is.. it only downloads the first file out of 3.

Fetching JSON data with .netcore from a web api

I made an api which have some data for example www.example.com/data/select?indent=on&q=title:asthma
gives the data in JSON format like
{
"responseHeader":{
"status":0,
"QTime":2,
"params":
{
"q":"title:asthma",
"indent":"on",
"wt":"json"
},
"response":{"numFound":1,"start":0, docs:[
{
"tstamp": "xxxx"
"id": "xxxxx"
"title": "Asthma is a medical term"
"url": "www.example.com/xxxx"
"content":"xxxxx"
}]}
}}
I want to call the same url from my .netcore application such that I can have title and url from the response and show it to my .netcore application.
As a new to .netcore it is pretty tricky to get used to MVC architecture. My model look like this
namespace searchEngineTesting.Models
{
public class SearchModel
{
public string Title {get; set;}
public string Source {get; set;}
}
}
How can I use controller that whenever triggers take a string as an input for example cancer and put it to the title of the api like www.example.com/data/select?indent=on&q=title:cancer and fetch the title and url from the response.
You could fetch json data like below:
[HttpGet]
public async Task<JsonResult> Get()
{
var model = new SearchModel();
var url = "https://localhost:5001/api/values/test";//it should be the url of your api
using (var httpClient = new HttpClient())
{
using (var response = await httpClient.GetAsync(url))
{
using (var content = response.Content)
{
//get the json result from your api
var result = await content.ReadAsStringAsync();
var root = (JObject)JsonConvert.DeserializeObject(result);
var items = root.SelectToken("responseHeader").Children().OfType<JProperty>().ToDictionary(p => p.Name, p => p.Value);
foreach(var item in items)
{
if(item.Key== "response")
{
var key = item.Value.SelectToken("").OfType<JProperty>().ToDictionary(p => p.Name, p => p.Value);
foreach (var k in key)
{
if(k.Key== "docs")
{
var tests = JsonConvert.DeserializeObject<JArray>(k.Value.ToString());
var data = k.Value.SelectToken("").Children().First();
var test = data.SelectToken("").Children().OfType<JProperty>().ToDictionary(p => p.Name, p => p.Value);
foreach (var t in test)
{
if (t.Key == "url")
{
model.Source = t.Value.ToString();
}
else if (t.Key=="title")
{
model.Title = t.Value.ToString();
}
}
}
}
}
}
return new JsonResult(model);
}
}
}
}
[HttpGet("[Action]")]
public string test()
{
//for easy testing,I just read your json file and return string
var jsonstring = System.IO.File.ReadAllText("C:\\test.json");
return jsonstring;
}

Read Asp.Net Core Response body in ActionFilterAttribute

I'm using Asp.Net Core as a Rest Api Service.
I need access to request and response in ActionFilter. Actually, I found the request in OnActionExcecuted but I can't read the response result.
I'm trying to return value as follow:
[HttpGet]
[ProducesResponseType(typeof(ResponseType), (int)HttpStatusCode.OK)]
[Route("[action]")]
public async Task<IActionResult> Get(CancellationToken cancellationToken)
{
var model = await _responseServices.Get(cancellationToken);
return Ok(model);
}
And in ActionFilter OnExcecuted method as follow:
_request = context.HttpContext.Request.ReadAsString().Result;
_response = context.HttpContext.Response.ReadAsString().Result; //?
I'm trying to get the response in ReadAsString as an Extension method as follow:
public static async Task<string> ReadAsString(this HttpResponse response)
{
var initialBody = response.Body;
var buffer = new byte[Convert.ToInt32(response.ContentLength)];
await response.Body.ReadAsync(buffer, 0, buffer.Length);
var body = Encoding.UTF8.GetString(buffer);
response.Body = initialBody;
return body;
}
But, there is no result!
How I can get the response in OnActionExcecuted?
Thanks, everyone for taking the time to try and help explain
If you're logging for json result/ view result , you don't need to read the whole response stream. Simply serialize the context.Result:
public class MyFilterAttribute : ActionFilterAttribute
{
private ILogger<MyFilterAttribute> logger;
public MyFilterAttribute(ILogger<MyFilterAttribute> logger){
this.logger = logger;
}
public override void OnActionExecuted(ActionExecutedContext context)
{
var result = context.Result;
if (result is JsonResult json)
{
var x = json.Value;
var status = json.StatusCode;
this.logger.LogInformation(JsonConvert.SerializeObject(x));
}
if(result is ViewResult view){
// I think it's better to log ViewData instead of the finally rendered template string
var status = view.StatusCode;
var x = view.ViewData;
var name = view.ViewName;
this.logger.LogInformation(JsonConvert.SerializeObject(x));
}
else{
this.logger.LogInformation("...");
}
}
I know there is already an answer but I want to also add that the problem is the MVC pipeline has not populated the Response.Body when running an ActionFilter so you cannot access it. The Response.Body is populated by the MVC middleware.
If you want to read Response.Body then you need to create your own custom middleware to intercept the call when the Response object has been populated. There are numerous websites that can show you how to do this. One example is here.
As discussed in the other answer, if you want to do it in an ActionFilter you can use the context.Result to access the information.
For logging whole request and response in the ASP.NET Core filter pipeline you can use Result filter attribute
public class LogRequestResponseAttribute : TypeFilterAttribute
{
public LogRequestResponseAttribute() : base(typeof(LogRequestResponseImplementation)) { }
private class LogRequestResponseImplementation : IAsyncResultFilter
{
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
var requestHeadersText = CommonLoggingTools.SerializeHeaders(context.HttpContext.Request.Headers);
Log.Information("requestHeaders: " + requestHeadersText);
var requestBodyText = await CommonLoggingTools.FormatRequestBody(context.HttpContext.Request);
Log.Information("requestBody: " + requestBodyText);
await next();
var responseHeadersText = CommonLoggingTools.SerializeHeaders(context.HttpContext.Response.Headers);
Log.Information("responseHeaders: " + responseHeadersText);
var responseBodyText = await CommonLoggingTools.FormatResponseBody(context.HttpContext.Response);
Log.Information("responseBody: " + responseBodyText);
}
}
}
In Startup.cs add
app.UseMiddleware<ResponseRewindMiddleware>();
services.AddScoped<LogRequestResponseAttribute>();
Somewhere add static class
public static class CommonLoggingTools
{
public static async Task<string> FormatRequestBody(HttpRequest request)
{
//This line allows us to set the reader for the request back at the beginning of its stream.
request.EnableRewind();
//We now need to read the request stream. First, we create a new byte[] with the same length as the request stream...
var buffer = new byte[Convert.ToInt32(request.ContentLength)];
//...Then we copy the entire request stream into the new buffer.
await request.Body.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
//We convert the byte[] into a string using UTF8 encoding...
var bodyAsText = Encoding.UTF8.GetString(buffer);
//..and finally, assign the read body back to the request body, which is allowed because of EnableRewind()
request.Body.Position = 0;
return $"{request.Scheme} {request.Host}{request.Path} {request.QueryString} {bodyAsText}";
}
public static async Task<string> FormatResponseBody(HttpResponse response)
{
//We need to read the response stream from the beginning...
response.Body.Seek(0, SeekOrigin.Begin);
//...and copy it into a string
string text = await new StreamReader(response.Body).ReadToEndAsync();
//We need to reset the reader for the response so that the client can read it.
response.Body.Seek(0, SeekOrigin.Begin);
response.Body.Position = 0;
//Return the string for the response, including the status code (e.g. 200, 404, 401, etc.)
return $"{response.StatusCode}: {text}";
}
public static string SerializeHeaders(IHeaderDictionary headers)
{
var dict = new Dictionary<string, string>();
foreach (var item in headers.ToList())
{
//if (item.Value != null)
//{
var header = string.Empty;
foreach (var value in item.Value)
{
header += value + " ";
}
// Trim the trailing space and add item to the dictionary
header = header.TrimEnd(" ".ToCharArray());
dict.Add(item.Key, header);
//}
}
return JsonConvert.SerializeObject(dict, Formatting.Indented);
}
}
public class ResponseRewindMiddleware {
private readonly RequestDelegate next;
public ResponseRewindMiddleware(RequestDelegate next) {
this.next = next;
}
public async Task Invoke(HttpContext context) {
Stream originalBody = context.Response.Body;
try {
using (var memStream = new MemoryStream()) {
context.Response.Body = memStream;
await next(context);
//memStream.Position = 0;
//string responseBody = new StreamReader(memStream).ReadToEnd();
memStream.Position = 0;
await memStream.CopyToAsync(originalBody);
}
} finally {
context.Response.Body = originalBody;
}
}
You can also do...
string response = "Hello";
if (result is ObjectResult objectResult)
{
var status = objectResult.StatusCode;
var value = objectResult.Value;
var stringResult = objectResult.ToString();
responce = (JsonConvert.SerializeObject(value));
}
I used this in a .net core app.
Hope it helps.

How to get file size in MultipartStreamProvider GetStream when missing from ContentDisposition

I creating a custom MultipartStreamProvider to store files in Azure File Storage as part of a Lift & Shift effort of a legacy application. The client is using AngularJS on the front end and WebAPI on the backend. When I am trying to use the MultipartStreamProvider, I need to implement GetStream to return a stream for it to write to. I am using cloudFile.OpenWrite which asks for the size of the stream/file that will be written to it. However, in GetStream, the ContentDisposition.Size is empty. Is there a way I can either make the AngularJS send the content size for each file or on the backend, maybe I can dig the size of the file stream from somewhere else? Any help would be greatly appreciated. Thanks!
MultipartStreamProvider
public override Stream GetStream(HttpContent parent, HttpContentHeaders headers)
{
// For form data, Content-Disposition header is a requirement
ContentDispositionHeaderValue contentDisposition = headers.ContentDisposition;
Console.WriteLine(files.Count);
if (contentDisposition != null)
{
// create default filename if its missing
contentDisposition.FileName = (String.IsNullOrEmpty(contentDisposition.FileName) ? $"{Guid.NewGuid()}.data" : contentDisposition.FileName);
// We won't post process files as form data
_isFormData.Add(false);
CloudMultipartFileData fileData = new CloudMultipartFileData(headers, _fileRepository.BaseUrl, contentDisposition.FileName);// your - aws - filelocation - url - maybe);
_fileData.Add(fileData);
var azureStream = _fileRepository.GetWriteStream(contentDisposition.Size, _relativeDirectory, fileData.FileName);
return azureStream;
// We will post process this as form data
_isFormData.Add(true);
}
throw new InvalidOperationException("Did not find required 'Content-Disposition' header field in MIME multipart body part..");
}
Actual Call to Azure
public override Stream GetWriteStream(long? fileSize, string relativeDirectory, string filename)
{
var combinedRelativeDirectory = GetCloudDirectory(relativeDirectory);
CloudFile cloudFile = combinedRelativeDirectory.GetFileReference(filename);
return cloudFile.OpenWrite(fileSize, null, null);
}
AngularJS File Upload Code
/********************************** Add/Upload Photos **************************************/
$scope.$watch('files', function (files) {
$scope.formUpload = false;
console.log(files);
if (files != null) {
for (var i = 0; i < files.length; i++) {
$scope.errorMsg = null;
(function (file) {
upload(file);
})(files[i]);
}
}
});
function upload(file) {
file.upload = Upload.upload({
url: window.location.origin + "/api/mydocs/uploadfile?storeFolder=" + $scope.attachmentFolder + "&storeId=" + $scope.storeId + "&userId=" + $scope.currentUser.UserId,
method: 'POST',
headers: {},
fields: {},
file: file
});
I wound up manually adding the file size to the header
function upload(file) {
file.upload = Upload.upload({
url: window.location.origin + "/api/mydocs/uploadfile?storeFolder=" + $scope.myFolder + "&clientId=" + $scope.clientId,
method: 'POST',
headers: { 'file-info':file.name + "-/" + file.size },
fields: {},
file: file
});
And then in the constructor, I use that to create a lookup table:
public MyCloudMultipartFormDataStreamProvider(string relativeDirectory, IEnumerable<string> lookupInfo)
{
NewFileNames = new Dictionary<string, string>();
_fileRepository = new CloudFileRepository();
_relativeDirectory = relativeDirectory;
_uploadedFilesLookup = new Dictionary<string, long>();
foreach (var fileInfo in lookupInfo)
{
var values = Regex.Split(fileInfo, #"-/");
_uploadedFilesLookup.Add(values[0], Int64.Parse(values[1]));
}
}
Then grab the file's size out of the lookup table and pass that to my GetWriteStream method
var azureStream = _fileFacade.GetWriteStream(_uploadedFilesLookup[fileName],
_relativeDirectory, fileData.FileName, out newFileName);

How to add property "Alternate" to an image file on server?

I'm using asp.net core and web api for uploading images.
On server:
[Produces("application/json")]
[Route("api/Upload")]
public class UploadApiController : Controller
{
private readonly IHostingEnvironment _environment;
public UploadApiController(IHostingEnvironment environment)
{
_environment = environment;
}
[HttpPost]
public async Task Post(ICollection<IFormFile> files)
{
//...
}
}
On client:
// Files is an array that contains all temporary images for uploading.
let Files = [];
let image_preview = function (file) {
file['Alternate'] = 'alternate_text';
Files.push(file);
// other implements...
};
$('button#upload').click(function () {
let formData = new FormData();
for (let i = 0; i < Files.length; i++) {
formData.append('files', Files[i])
}
let xhr = new XMLHttpRequest();
xhr.open('POST', '/api/upload', true);
xhr.onload = function () {
console.log('uploading...')
};
xhr.send(formData);
});
Snapshot:
My question: how to add new property "Alternate" to ICollection<IFormFile> files to detect property Alternate that is sent from client (formData)?
It's not the answer for question How to add property “Alternate” to an image file on server? but it seems like solving the problem (sending image file with alternate text).
On server:
using Newtonsoft.Json;
[HttpPost]
public async Task Post(ICollection<IFormFile> files, IList<string> alts)
{
IDictionary<string, string> _alts = new Dictionary<string, string>();
foreach (var alt in alts)
{
IDictionary<string, string> temp = JsonConvert.DeserializeObject<Dictionary<string, string>>(alt);
foreach (var item in temp)
{
_alts.Add(item.Key, item.Value);
}
}
}
On client:
for (let i = 0; i < Files.length; i++) {
formData.append('files', Files[i]);
let name = Files[i]['name'],
alt = {};
alt[name] = 'alt_text';
formData.append('alts', JSON.stringify(alt));
}
We would never get duplicate key in the dictionary because Files[i]['name'] is always primary and cannot be changed (hacking by someone) if we've checked duplicate uploading file before.
Then, we can merge the file name (in files) with Key in _alts to get Alternate text.
Snapshot:
UPDATE: The code in the snapshot was wrong.

Resources