how to get origin returned object - c#

Get["/"] = _ =>"some data";
Post["/"] = _ =>new {detail="detail.."};
I need convert response to this format
{
state: state code
data: origin data
}
So, I add a after hook
After.AddItemToEndOfPipeline(ResponseFormatHook);
...
private void ResponseFormatHook(NancyContext ctx)
{
var apiResponse = new APIResponse();
apiResponse.State = ctx.Response.StatusCode;
using(var stream = new MemoryStream())
{
ctx.Response.Contents.Invoke(stream);
stream.Position = 0;
using (var reader = new StreamReader(stream))
{
// get the origin data
var content = reader.ReadToEnd();
apiResponse.Data = content;
}
}
var response = new JsonResponse(apiResponse, new DefaultJsonSerializer());
response.StatusCode = HttpStatusCode.OK;
ctx.Response = response;
}
For Get["/"],it's ok. will return {state:200,data:"some data"}.
But for Post["/"],will return {state:200,data:"{detail:\"detail..\"}"}.The data convert to a string not object.The client can not deserialize at once.
So, how can I get the origin data?
apiResponse.Data=OriginData, this will be OK.
EDIT 1
Maybe I can deserialize the Data like
apiResponse.Data=JsonConvert.Deserialize(apiResponse.Data).
But I think this cost too much,isn't it?

Use IResponseProcessor will touch the origin data.
public class APIResponseProcessor : IResponseProcessor
{
private static readonly IEnumerable<Tuple<string, MediaRange>> extensionMappings =
new[] {new Tuple<string, MediaRange>("json", MediaRange.FromString("application/json"))};
public ProcessorMatch CanProcess(MediaRange requestedMediaRange, dynamic model, NancyContext context)
{
var match = new ProcessorMatch();
match.ModelResult = MatchResult.DontCare;
match.RequestedContentTypeResult = MatchResult.ExactMatch;
return match;
}
public Response Process(MediaRange requestedMediaRange, dynamic model, NancyContext context)
{
var apiResponse = new APIResponse();
apiResponse.Data = model;
return new JsonResponse(apiResponse,new DefaultJsonSerializer());
}
public IEnumerable<Tuple<string, MediaRange>> ExtensionMappings { get { return extensionMappings; } }
}
Use processor instead of After hook.

Related

Get/generate shared link for a uploaded file in Google Drive API v3

I am uploading files to a Gdrive follow this instructions. In the File objtect I just set the name, like this:
{
"name": "myObjectName"
}
The files are uploading without problem. Now I need to generate a shared link for each upload file, Do you know who is the request that I have to do?
Thanks,
With #Jacques-Guzel HeronĀ“s help, I completed the upload file process. I created a wrapper for Google Drive API v3. That is my code if any need to do something like that: (I am using C#):
public interface IGDriveApiV3Wrapper
{
string UploadFile(string filePath, string gDriveUploadDestinationFolderId = null);
bool SetFilePermissions(string fileId, GDriveFileRole gDriveRole, GDriveFileType gDriveType);
GDriveFile GetFileInfo(string fileId);
}
public class GDriveApiV3NativeWrapper : IGDriveApiV3Wrapper
{
private const string GDriveFilesApiResumablePath = "https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable";
private const string GDriveTokenApiPath = "https://oauth2.googleapis.com/token";
private static readonly HttpClient GDriveClient = new HttpClient { Timeout = Timeout.InfiniteTimeSpan };
private readonly List<KeyValuePair<string, string>> _getTokenRequestContent;
private static GDriveTokenInfo _gDriveTokenInfo;
private static readonly object UpdateGDriveTokenInfoLocker = new object();
public GDriveApiV3NativeWrapper(string gDriveApiClientId, string gDriveApiClientSecret, string gDriveApiRefreshToken)
{
_getTokenRequestContent = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("client_id", gDriveApiClientId),
new KeyValuePair<string, string>("client_secret", gDriveApiClientSecret),
new KeyValuePair<string, string>("refresh_token", gDriveApiRefreshToken),
new KeyValuePair<string, string>("grant_type", "refresh_token")
};
}
public string UploadFile(string filePath, string gDriveUploadDestinationFolderId = null)
{
if (string.IsNullOrEmpty(filePath))
throw new ArgumentException("Value cannot be null or empty.", nameof(filePath));
FileInfo fileInfo;
try
{
fileInfo = new FileInfo(filePath);
}
catch (Exception ex)
{
throw new ArgumentException("File not valid.", nameof(filePath), ex);
}
if (!fileInfo.Exists)
throw new ArgumentException("File not exists.", nameof(filePath));
using (var initiateResumableUploadSessionRequest = new HttpRequestMessage(HttpMethod.Post, GDriveFilesApiResumablePath))
{
UpdateGDriveTokenInfo();
initiateResumableUploadSessionRequest.Headers.Authorization = new AuthenticationHeaderValue(_gDriveTokenInfo.TokenType, _gDriveTokenInfo.AccessToken);
var jsonContent = new JObject(
new JProperty("name", fileInfo.Name));
if (!string.IsNullOrEmpty(gDriveUploadDestinationFolderId))
{
jsonContent.Add(new JProperty("parents", new JArray { gDriveUploadDestinationFolderId }));
}
initiateResumableUploadSessionRequest.Content = new StringContent(jsonContent.ToString(), Encoding.UTF8, "application/json");
var initiateResumableUploadSessionResponse = GDriveClient.SendAsync(initiateResumableUploadSessionRequest).Result;
if (initiateResumableUploadSessionResponse.StatusCode != HttpStatusCode.OK)
throw new ExternalException(initiateResumableUploadSessionResponse.ToString());
using (var uploadFileRequest = new HttpRequestMessage(HttpMethod.Put, initiateResumableUploadSessionResponse.Headers.Location))
{
uploadFileRequest.Content = new ByteArrayContent(File.ReadAllBytes(filePath));
HttpResponseMessage uploadFileResponse;
uploadFileResponse = GDriveClient.SendAsync(uploadFileRequest).Result;
if (uploadFileResponse.StatusCode != HttpStatusCode.OK && uploadFileResponse.StatusCode != HttpStatusCode.Created)
throw new ExternalException(uploadFileResponse.ReasonPhrase);
var uploadFileResponseBody = uploadFileResponse.Content.ReadAsStringAsync().Result;
JObject uploadFileResponseJson = JObject.Parse(uploadFileResponseBody);
return uploadFileResponseJson["id"].ToString();
}
}
}
public bool SetFilePermissions(string fileId, GDriveFileRole gDriveFileRole, GDriveFileType gDriveFileType)
{
if (string.IsNullOrEmpty(fileId))
throw new ArgumentException("Value cannot be null or empty.", nameof(fileId));
using (var setFilePermissionsRequest = new HttpRequestMessage(HttpMethod.Post, $"https://www.googleapis.com/drive/v3/files/{fileId}/permissions"))
{
UpdateGDriveTokenInfo();
setFilePermissionsRequest.Headers.Authorization = new AuthenticationHeaderValue(_gDriveTokenInfo.TokenType, _gDriveTokenInfo.AccessToken);
var jsonContent2 = new JObject(
new JProperty("role", gDriveFileRole.ToString().ToLower()),
new JProperty("type", gDriveFileType.ToString().ToLower()));
setFilePermissionsRequest.Content = new StringContent(jsonContent2.ToString(), Encoding.UTF8, "application/json");
HttpResponseMessage setFilePermissionsResponse = GDriveClient.SendAsync(setFilePermissionsRequest).Result;
if (setFilePermissionsResponse.StatusCode != HttpStatusCode.OK)
throw new ExternalException(setFilePermissionsResponse.ToString());
}
return true;
}
public GDriveFile GetFileInfo(string fileId)
{
using (var getFileInfoRequest = new HttpRequestMessage(HttpMethod.Get, $"https://www.googleapis.com/drive/v3/files/{fileId}?fields=name,webViewLink"))
{
UpdateGDriveTokenInfo();
getFileInfoRequest.Headers.Authorization = new AuthenticationHeaderValue(_gDriveTokenInfo.TokenType, _gDriveTokenInfo.AccessToken);
HttpResponseMessage getFileInfoResponse = GDriveClient.SendAsync(getFileInfoRequest).Result;
if (getFileInfoResponse.StatusCode != HttpStatusCode.OK)
throw new ExternalException(getFileInfoResponse.ToString());
var getFileInfoResponseBody = getFileInfoResponse.Content.ReadAsStringAsync().Result;
JObject getFileInfoResponseJson = JObject.Parse(getFileInfoResponseBody);
return new GDriveFile
{
Id = fileId,
Name = getFileInfoResponseJson["name"].ToString(),
WebViewLink = getFileInfoResponseJson["webViewLink"].ToString()
};
}
}
private void UpdateGDriveTokenInfo()
{
lock (UpdateGDriveTokenInfoLocker)
{
if (_gDriveTokenInfo != null && !_gDriveTokenInfo.IsExpired())
{
return;
}
using (var refreshTokenRequest = new HttpRequestMessage(HttpMethod.Post, GDriveTokenApiPath))
{
refreshTokenRequest.Content = new FormUrlEncodedContent(_getTokenRequestContent);
var getTokenRequestResponse = GDriveClient.SendAsync(refreshTokenRequest).Result;
var jsonResponse = JObject.Parse(getTokenRequestResponse.Content.ReadAsStringAsync().Result);
_gDriveTokenInfo = new GDriveTokenInfo((string)jsonResponse["access_token"], (int)jsonResponse["expires_in"], (string)jsonResponse["token_type"]);
}
}
}
public class GDriveFile
{
public string Id { get; set; }
public string Name { get; set; }
public string WebViewLink { get; set; }
}
public enum GDriveFileRole
{
Owner,
Organizer,
FileOrganizer,
Writer,
Commenter,
Reader
}
public enum GDriveFileType
{
User,
Group,
Domain,
Anyone
}
public class Program
{
private static IGDriveApiV3Wrapper _gDriveApiV3Wrapper;
private readonly string _gDriveUploadDestinationFolderId;
public Program(IGDriveApiV3Wrapper gDriveApiV3Wrapper, string gDriveUploadDestinationFolderId = null)
{
_gDriveApiV3Wrapper = gDriveApiV3Wrapper;
_gDriveUploadDestinationFolderId = gDriveUploadDestinationFolderId;
}
public string Upload(string filePath)
{
string fileId = _gDriveApiV3Wrapper.UploadFile(filePath, _gDriveUploadDestinationFolderId);
_gDriveApiV3Wrapper.SetFilePermissions(fileId, GDriveFileRole.Reader, GDriveFileType.Anyone);
GDriveFile gDriveFile = _gDriveApiV3Wrapper.GetFileInfo(fileId);
return gDriveFile.WebViewLink;
}
}
I'll assume that you are using Drive API v3. To set up permissions after uploading the file you have to use the method create. You can check the sharing documentation to know more about the different ways of creation of permissions and its different levels of access. After sharing the file, you can retrieve the link with the property webViewLink of the file. If you still have any doubts, please ask me for further clarification.
The Class GDrivenTokenInfo i don`t see its code

Implementing a generic API caller

I am trying to implement a generic caller that uses OpenWeatherMap's different weather API's, but I got stuck in regards to how I would put in the right identifier for the link.
.../weather?q=... returns JSON data for the current weather;
.../forecast?q=... returns JSON data for a five day forecast.
I am looking for the textbook way to maybe retrieve the API type of each class through accessing GetAPIType(), cast that to an int and put it in the index, so that I would be able to use identifiers[index]. Or perhaps there is an easier way to do it.
Checking for the typeof(T) also crossed my mind, and I would assign the index depending on the if(typeof(T).Equals(typeof(...))) construct, but that seems very messy and if OpenWeatherMap had 100 API's in theory, I would need 100 different if constructs. With this in mind, wouldn't creating those checks beat the purpose of Client being generic?
A third solution I thought of would be passing APIType type as a parameter for the Client constructor,
e.g. var client = new Client<CurrentWeatherDTO>(APIType.CurrentWeather, location, apiKey),
but given the fact that Client is generic and I already provide a type when I instantiate it, it would seem awfully redundant.
Client.cs
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using System.Reflection;
namespace Rainy.OpenWeatherMapAPI
{
public class Client<T>
{
private readonly string location;
private readonly string apiKey;
private readonly string requestUri;
private readonly string[] identifiers = { "weather", "forecast" };
private readonly int index;
public Client(string location, string apiKey)
{
// Get the type of API used in order to get the right identifier for the link.
// ??? Maybe use Reflection, somehow.
this.location = location;
this.apiKey = apiKey;
requestUri = $"api.openweathermap.org/data/2.5/{}?q={location}&appid={apiKey}";
}
public async Task<T> GetWeather(CancellationToken cancellationToken)
{
using (var client = new HttpClient())
using (var request = new HttpRequestMessage(HttpMethod.Get, requestUri))
using (var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken))
{
var stream = await response.Content.ReadAsStreamAsync();
if (response.IsSuccessStatusCode)
return DeserializeJsonFromStream<T>(stream);
var content = await StreamToStringAsync(stream);
throw new APIException
{
StatusCode = (int)response.StatusCode,
Content = content
};
}
}
private U DeserializeJsonFromStream<U>(Stream stream)
{
if (stream == null || stream.CanRead == false)
return default(U);
using (var sr = new StreamReader(stream))
using (var jtr = new JsonTextReader(sr))
{
var js = new JsonSerializer();
var searchResult = js.Deserialize<U>(jtr);
return searchResult;
}
}
private async Task<string> StreamToStringAsync(Stream stream)
{
string content = null;
if (stream != null)
using (var sr = new StreamReader(stream))
content = await sr.ReadToEndAsync();
return content;
}
}
}
APIType.cs
namespace Rainy.OpenWeatherMapAPI
{
public enum APIType
{
CurrentWeather = 0,
FiveDayForecast = 1
}
}
IWeather.cs
namespace Rainy.OpenWeatherMapAPI
{
public interface IWeather
{
APIType GetAPIType();
}
}
CurrentWeatherDTO.cs
namespace Rainy.OpenWeatherMapAPI.CurrentWeatherData
{
class CurrentWeatherDTO : IWeather
{
public APIType GetAPIType()
{
return APIType.CurrentWeather;
}
}
}
FiveDayForecastDTO.cs
namespace Rainy.OpenWeatherMapAPI.WeatherForecastData
{
class FiveDayForecastDTO : IWeather
{
public APIType GetAPIType()
{
return APIType.FiveDayForecast;
}
}
}
I would not use an enum to drive the index of an array.
I would directly return the string in a static way.
This solution can also work with the index of the array if you want.
Here is the code and the dotnetfiddle:
using System;
public class Program
{
public static void Main()
{
var client1 = new Client<CurrentWeatherDTO>(null);
Console.WriteLine("Client CurrentWeather type: " + client1.Type);
var client2 = new Client<FiveDayForecastDTO>(null);
Console.WriteLine("Client FiveDay type: " + client2.Type);
}
public class Client<T> where T : IWeather, new()
{
public string Type { get; set; }
public Client(string apiKey)
{
var dto = (IWeather)new T();
this.Type = dto.GetAPIType();
}
}
public static class APIType
{
public static string CurrentWeather = "weather";
public static string FiveDayForecast = "forecast";
}
public interface IWeather
{
string GetAPIType();
}
class CurrentWeatherDTO : IWeather
{
public string GetAPIType()
{
return APIType.CurrentWeather;
}
}
class FiveDayForecastDTO : IWeather
{
public string GetAPIType()
{
return APIType.FiveDayForecast;
}
}
}
I would probably use a solution like this, but maybe a bit more error handling.
There's a couple of references for how to use HttpClient.
I don't really understand the part in the requestUri with {}, maybe that's part of your problem, I changed it to {???} in my sample code.
class Client
{
// Problems using HttpClient and look into using IHttpClientFactory...
// http://byterot.blogspot.com/2016/07/singleton-httpclient-dns.html
// https://www.hanselman.com/blog/HttpClientFactoryForTypedHttpClientInstancesInASPNETCore21.aspx
static HttpClient _httpClient = new HttpClient();
readonly string WeatherUri = $"api.openweathermap.org/data/2.5/{???}?q={0}&appid={1}";
public async Task<T> GetWeather<T>(string location, CancellationToken cancellationToken)
{
var apiKey = ApiKeyAttribute.GetApiKey<T>();
if (apiKey == null) throw new Exception("ApiKeyAttirbute missing");
var requestUri = string.Format(WeatherUri, location, apiKey);
return await GetItem<T>(requestUri, cancellationToken);
}
public async Task<T> GetItem<T>(string requestUri, CancellationToken cancellationToken)
{
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, requestUri);
var response = await _httpClient.SendAsync(httpRequestMessage, cancellationToken);
if (!response.IsSuccessStatusCode) throw new Exception("Error requesting data");
if (response.Content == null) return default(T);
var content = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T>(content);
}
}
[ApiKeyAttribute("weather")]
class CurrentWeatherDTO { /* add appropriat properties */ }
[ApiKeyAttribute("forecast")]
class FiveDayForecastDTO { /* add appropriat properties */ }
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
class ApiKeyAttribute : Attribute
{
public string Name { get; private set; }
public ApiKeyAttribute(string name)
{
Name = name;
}
public static string GetApiKey<T>()
{
var attribute = typeof(T).GetCustomAttribute<ApiKeyAttribute>();
return attribute?.Name;
}
}

UWP Json to C# conversion

I want to serialize some json data I get from the web to classes and use the data, so I went to http://json2csharp.com/ and turned the json as below
json: [{"line_descr":"\u03a0\u0395\u0399\u03a1\u0391\u0399\u0391\u03a3 -
\u0392\u039f\u03a5\u039b\u0391","line_descr_eng":"PEIRAIAS - VOYLA"}]
To this class:
public class RootObject
{
public string line_descr { get; set; }
public string line_descr_eng { get; set; }
}
This is my code:
class LineName
{
public async static Task<RootObject> GetLineName(int linecode)
{
var http = new HttpClient();
var response = await http.GetAsync("http://telematics.oasa.gr/api/?act=getLineName&p1=962");
var result = await response.Content.ReadAsStringAsync();
var serializer = new DataContractJsonSerializer(typeof(RootObject));
var ms = new MemoryStream(Encoding.UTF8.GetBytes(result));
var data = (RootObject)serializer.ReadObject(ms);
return data;
}
}
[DataContract]
public class RootObject
{
[DataMember]
public string line_descr { get; set; }
[DataMember]
public string line_descr_eng { get; set; }
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
RootObject myLine = await LineName.GetLineName(92);
ResultTextBlock.Text = myLine.line_descr_eng;
}
So when I try to get the data and display it in my textblock I get the error: line_descr_eng is null.
Can someone point where the fault is ? since the line_descr_eng should be
PEIRAIAS - VOYLA but mine is null and after a lot of searching I cant find where the fault is.
Your json is an array, not an object, and you should deserialize it into an array.
public async static Task<RootObject[]> GetLineName(int linecode)
{
var http = new HttpClient();
var response = await http.GetAsync("http://telematics.oasa.gr/api/?act=getLineName&p1=962");
var result = await response.Content.ReadAsStringAsync();
var serializer = new DataContractJsonSerializer(typeof(RootObject[]));
var ms = new MemoryStream(Encoding.UTF8.GetBytes(result));
var data = (RootObject[])serializer.ReadObject(ms);
return data;
}
//...
var myLines = await LineName.GetLineName(92);
var myLine = myLines.FirstOrDefault();
Also you don't need a memory stream, you can read stream from the http response
var result = await response.Content.ReadAsStreamAsync();
You simple can use the JavaScriptSerializer class instead of DataContractJsonSerializer like this:
Replace:
var serializer = new DataContractJsonSerializer(typeof(RootObject));
var ms = new MemoryStream(Encoding.UTF8.GetBytes(result));
var data = (RootObject)serializer.ReadObject(ms);
with this:
var ser = new JavaScriptSerializer();
var test = ser.Deserialize<List<RootObject>>(json);
If you cannot find JavaScriptSerializer, then you have to do the simple following steps:
Right click References and do Add Reference, then from Assemblies->Framework select System.Web.Extensions.
Now you should be able to add the following to your class file:
using System.Web.Script.Serialization;
Cited from: https://stackoverflow.com/a/15391388/5056173

ObjectDisposedException when trying to upload a file

I have this service class:
public delegate string AsyncMethodCaller(string id, HttpPostedFileBase file);
public class ObjectService : IDisposable
{
private readonly IObjectRepository repository;
private readonly IAmazonS3 client;
private readonly string bucketName;
private static object syncRoot = new object();
private static IDictionary<string, int> processStatus { get; set; }
public ObjectService(string accessKey, string secretKey, string bucketName)
{
var credentials = new BasicAWSCredentials(accessKey, secretKey);
this.bucketName = bucketName;
this.client = new AmazonS3Client(credentials, RegionEndpoint.EUWest1);
this.repository = new ObjectRepository(this.client, this.bucketName);
if (processStatus == null)
processStatus = new Dictionary<string, int>();
}
public IList<S3Object> GetAll()
{
return this.repository.GetAll();
}
public S3Object Get(string key)
{
return this.GetAll().Where(model => model.Key.Equals(key, StringComparison.OrdinalIgnoreCase)).SingleOrDefault();
}
/// <summary>
/// Note: You can upload objects of up to 5 GB in size in a single operation. For objects greater than 5 GB you must use the multipart upload API.
/// Using the multipart upload API you can upload objects up to 5 TB each. For more information, see http://docs.aws.amazon.com/AmazonS3/latest/dev/uploadobjusingmpu.html.
/// </summary>
/// <param name="id">Unique id for tracking the upload progress</param>
/// <param name="bucketName">The name of the bucket that the object is being uploaded to</param>
/// <param name="file">The file that will be uploaded</param>
/// <returns>The unique id</returns>
public string Upload(string id, HttpPostedFileBase file)
{
var reader = new BinaryReader(file.InputStream);
var data = reader.ReadBytes((int)file.InputStream.Length);
var stream = new MemoryStream(data);
var utility = new TransferUtility(client);
var request = new TransferUtilityUploadRequest()
{
BucketName = this.bucketName,
Key = file.FileName,
InputStream = stream
};
request.UploadProgressEvent += (sender, e) => request_UploadProgressEvent(sender, e, id);
utility.Upload(request);
return id;
}
private void request_UploadProgressEvent(object sender, UploadProgressArgs e, string id)
{
lock (syncRoot)
{
processStatus[id] = e.PercentDone;
}
}
public void Add(string id)
{
lock (syncRoot)
{
processStatus.Add(id, 0);
}
}
public void Remove(string id)
{
lock (syncRoot)
{
processStatus.Remove(id);
}
}
public int GetStatus(string id)
{
lock (syncRoot)
{
if (processStatus.Keys.Count(x => x == id) == 1)
{
return processStatus[id];
}
else
{
return 100;
}
}
}
public void Dispose()
{
this.repository.Dispose();
this.client.Dispose();
}
}
and my controller looks like this:
public class _UploadController : Controller
{
public void StartUpload(string id, HttpPostedFileBase file)
{
var bucketName = CompanyProvider.CurrentCompanyId();
using (var service = new ObjectService(ConfigurationManager.AppSettings["AWSAccessKey"], ConfigurationManager.AppSettings["AWSSecretKey"], bucketName))
{
service.Add(id);
var caller = new AsyncMethodCaller(service.Upload);
var result = caller.BeginInvoke(id, file, new AsyncCallback(CompleteUpload), caller);
}
}
public void CompleteUpload(IAsyncResult result)
{
var caller = (AsyncMethodCaller)result.AsyncState;
var id = caller.EndInvoke(result);
}
//
// GET: /_Upload/GetCurrentProgress
public JsonResult GetCurrentProgress(string id)
{
try
{
var bucketName = CompanyProvider.CurrentCompanyId();
this.ControllerContext.HttpContext.Response.AddHeader("cache-control", "no-cache");
using (var service = new ObjectService(ConfigurationManager.AppSettings["AWSAccessKey"], ConfigurationManager.AppSettings["AWSSecretKey"], bucketName))
{
return new JsonResult { Data = new { success = true, progress = service.GetStatus(id) } };
}
}
catch (Exception ex)
{
return new JsonResult { Data = new { success = false, error = ex.Message } };
}
}
}
Now, I have found that sometimes, I get the error ObjectDisposedException when trying to upload a file on this line: var data = reader.ReadBytes((int)file.InputStream.Length);. I read that I should not be using the using keyword because of the asynchronous calls but it still seems to be disposing the stream.
Can anyone tell me why?
Update 1
I have changed my controller to this:
private ObjectService service = new ObjectService(ConfigurationManager.AppSettings["AWSAccessKey"], ConfigurationManager.AppSettings["AWSSecretKey"], CompanyProvider.CurrentCompanyId());
public void StartUpload(string id, HttpPostedFileBase file)
{
service.Add(id);
var caller = new AsyncMethodCaller(service.Upload);
var result = caller.BeginInvoke(id, file, new AsyncCallback(CompleteUpload), caller);
}
public void CompleteUpload(IAsyncResult result)
{
var caller = (AsyncMethodCaller)result.AsyncState;
var id = caller.EndInvoke(result);
this.service.Dispose();
}
but I am still getting the error on the file.InputStream line.
Update 2
The problem seems to be with the BinaryReader.
I changed the code to look like this:
var inputStream = file.InputStream;
var i = inputStream.Length;
var n = (int)i;
using (var reader = new BinaryReader(inputStream))
{
var data = reader.ReadBytes(n);
var stream = new MemoryStream(data);
var request = new TransferUtilityUploadRequest()
{
BucketName = this.bucketName,
Key = file.FileName,
InputStream = stream
};
try
{
request.UploadProgressEvent += (sender, e) => request_UploadProgressEvent(sender, e, id);
utility.Upload(request);
}
catch
{
file.InputStream.Dispose(); // Close our stream
}
}
If the upload fails and I try to re-upload the item, that is when the error is thrown. It is like the item is locked or something.
You are disposing the service with the using statement when you are calling the service with BeginInvoke.
using (var service = new ObjectService(ConfigurationManager.AppSettings["AWSAccessKey"], ConfigurationManager.AppSettings["AWSSecretKey"], bucketName))
{
service.Add(id);
var caller = new AsyncMethodCaller(service.Upload);
var result = caller.BeginInvoke(id, file, new AsyncCallback(CompleteUpload), caller);
}
You have to dispose your service when the job is done:
var service = new ObjectService(ConfigurationManager.AppSettings["AWSAccessKey"], ConfigurationManager.AppSettings["AWSSecretKey"], bucketName)
public void StartUpload(string id, HttpPostedFileBase file)
{
var bucketName = CompanyProvider.CurrentCompanyId();
service.Add(id);
var caller = new AsyncMethodCaller(service.Upload);
var result = caller.BeginInvoke(id, file, new AsyncCallback(CompleteUpload), caller);
}
public void CompleteUpload(IAsyncResult result)
{
var caller = (AsyncMethodCaller)result.AsyncState;
var id = caller.EndInvoke(result);
service.Close();
service.Dispose();
}
Also your file might be corrupted, try this code:
byte[] buffer = new byte[file.InputStream.Length];
file.InputStream.Seek(0, SeekOrigin.Begin);
file.InputStream.Read(buffer, 0, file.InputStream.Length);

Webapi formdata upload (to DB) with extra parameters

I need to upload file sending extra paramaters.
I have found the following post in stackoverflow: Webapi ajax formdata upload with extra parameters
It describes how to do this using MultipartFormDataStreamProvider and saving data to fileserver. I do not need to save file to server, but to DB instead.
And I have already working code using MultipartMemoryStreamProvider, but it doesn't use extra parameter.
Can you give me clues how to process extra paramaters in webapi?
For example, if I add file and also test paramater:
data.append("myParameter", "test");
Here is my webapi that processes fileupload without extra paramater:
if (Request.Content.IsMimeMultipartContent())
{
var streamProvider = new MultipartMemoryStreamProvider();
var task = Request.Content.ReadAsMultipartAsync(streamProvider).ContinueWith<IEnumerable<FileModel>>(t =>
{
if (t.IsFaulted || t.IsCanceled)
{
throw new HttpResponseException(HttpStatusCode.InternalServerError);
}
_fleDataService = new FileDataBLL();
FileData fle;
var fleInfo = streamProvider.Contents.Select(i => {
fle = new FileData();
fle.FileName = i.Headers.ContentDisposition.FileName;
var contentTest = i.ReadAsByteArrayAsync();
contentTest.Wait();
if (contentTest.Result != null)
{
fle.FileContent = contentTest.Result;
}
// get extra parameters here ??????
_fleDataService.Save(fle);
return new FileModel(i.Headers.ContentDisposition.FileName, 1024); //todo
});
return fleInfo;
});
return task;
}
Expanding on gooid's answer, I encapsulated the FormData extraction into the provider because I was having issues with it being quoted. This just provided a better implementation in my opinion.
public class MultipartFormDataMemoryStreamProvider : MultipartMemoryStreamProvider
{
private readonly Collection<bool> _isFormData = new Collection<bool>();
private readonly NameValueCollection _formData = new NameValueCollection(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, Stream> _fileStreams = new Dictionary<string, Stream>();
public NameValueCollection FormData
{
get { return _formData; }
}
public Dictionary<string, Stream> FileStreams
{
get { return _fileStreams; }
}
public override Stream GetStream(HttpContent parent, HttpContentHeaders headers)
{
if (parent == null)
{
throw new ArgumentNullException("parent");
}
if (headers == null)
{
throw new ArgumentNullException("headers");
}
var contentDisposition = headers.ContentDisposition;
if (contentDisposition == null)
{
throw new InvalidOperationException("Did not find required 'Content-Disposition' header field in MIME multipart body part.");
}
_isFormData.Add(String.IsNullOrEmpty(contentDisposition.FileName));
return base.GetStream(parent, headers);
}
public override async Task ExecutePostProcessingAsync()
{
for (var index = 0; index < Contents.Count; index++)
{
HttpContent formContent = Contents[index];
if (_isFormData[index])
{
// Field
string formFieldName = UnquoteToken(formContent.Headers.ContentDisposition.Name) ?? string.Empty;
string formFieldValue = await formContent.ReadAsStringAsync();
FormData.Add(formFieldName, formFieldValue);
}
else
{
// File
string fileName = UnquoteToken(formContent.Headers.ContentDisposition.FileName);
Stream stream = await formContent.ReadAsStreamAsync();
FileStreams.Add(fileName, stream);
}
}
}
private static string UnquoteToken(string token)
{
if (string.IsNullOrWhiteSpace(token))
{
return token;
}
if (token.StartsWith("\"", StringComparison.Ordinal) && token.EndsWith("\"", StringComparison.Ordinal) && token.Length > 1)
{
return token.Substring(1, token.Length - 2);
}
return token;
}
}
And here's how I'm using it. Note that I used await since we're on .NET 4.5.
[HttpPost]
public async Task<HttpResponseMessage> Upload()
{
if (!Request.Content.IsMimeMultipartContent())
{
return Request.CreateResponse(HttpStatusCode.UnsupportedMediaType, "Unsupported media type.");
}
// Read the file and form data.
MultipartFormDataMemoryStreamProvider provider = new MultipartFormDataMemoryStreamProvider();
await Request.Content.ReadAsMultipartAsync(provider);
// Extract the fields from the form data.
string description = provider.FormData["description"];
int uploadType;
if (!Int32.TryParse(provider.FormData["uploadType"], out uploadType))
{
return Request.CreateResponse(HttpStatusCode.BadRequest, "Upload Type is invalid.");
}
// Check if files are on the request.
if (!provider.FileStreams.Any())
{
return Request.CreateResponse(HttpStatusCode.BadRequest, "No file uploaded.");
}
IList<string> uploadedFiles = new List<string>();
foreach (KeyValuePair<string, Stream> file in provider.FileStreams)
{
string fileName = file.Key;
Stream stream = file.Value;
// Do something with the uploaded file
UploadManager.Upload(stream, fileName, uploadType, description);
// Keep track of the filename for the response
uploadedFiles.Add(fileName);
}
return Request.CreateResponse(HttpStatusCode.OK, "Successfully Uploaded: " + string.Join(", ", uploadedFiles));
}
You can achieve this in a not-so-very-clean manner by implementing a custom DataStreamProvider that duplicates the logic for parsing FormData from multi-part content from MultipartFormDataStreamProvider.
I'm not quite sure why the decision was made to subclass MultipartFormDataStreamProvider from MultiPartFileStreamProvider without at least extracting the code that identifies and exposes the FormData collection since it is useful for many tasks involving multi-part data outside of simply saving a file to disk.
Anyway, the following provider should help solve your issue. You will still need to ensure that when you iterate the provider content you are ignoring anything that does not have a filename (specifically the statement streamProvider.Contents.Select() else you risk trying to upload the formdata to the DB). Hence the code that asks the provider is a HttpContent IsStream(), this is a bit of a hack but was the simplest was I could think to do it.
Note that it is basically a cut and paste hatchet job from the source of MultipartFormDataStreamProvider - it has not been rigorously tested (inspired by this answer).
public class MultipartFormDataMemoryStreamProvider : MultipartMemoryStreamProvider
{
private readonly Collection<bool> _isFormData = new Collection<bool>();
private readonly NameValueCollection _formData = new NameValueCollection(StringComparer.OrdinalIgnoreCase);
public NameValueCollection FormData
{
get { return _formData; }
}
public override Stream GetStream(HttpContent parent, HttpContentHeaders headers)
{
if (parent == null) throw new ArgumentNullException("parent");
if (headers == null) throw new ArgumentNullException("headers");
var contentDisposition = headers.ContentDisposition;
if (contentDisposition != null)
{
_isFormData.Add(String.IsNullOrEmpty(contentDisposition.FileName));
return base.GetStream(parent, headers);
}
throw new InvalidOperationException("Did not find required 'Content-Disposition' header field in MIME multipart body part.");
}
public override async Task ExecutePostProcessingAsync()
{
for (var index = 0; index < Contents.Count; index++)
{
if (IsStream(index))
continue;
var formContent = Contents[index];
var contentDisposition = formContent.Headers.ContentDisposition;
var formFieldName = UnquoteToken(contentDisposition.Name) ?? string.Empty;
var formFieldValue = await formContent.ReadAsStringAsync();
FormData.Add(formFieldName, formFieldValue);
}
}
private static string UnquoteToken(string token)
{
if (string.IsNullOrWhiteSpace(token))
return token;
if (token.StartsWith("\"", StringComparison.Ordinal) && token.EndsWith("\"", StringComparison.Ordinal) && token.Length > 1)
return token.Substring(1, token.Length - 2);
return token;
}
public bool IsStream(int idx)
{
return !_isFormData[idx];
}
}
It can be used as follows (using TPL syntax to match your question):
[HttpPost]
public Task<string> Post()
{
if (!Request.Content.IsMimeMultipartContent())
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "Invalid Request!"));
var provider = new MultipartFormDataMemoryStreamProvider();
return Request.Content.ReadAsMultipartAsync(provider).ContinueWith(p =>
{
var result = p.Result;
var myParameter = result.FormData.GetValues("myParameter").FirstOrDefault();
foreach (var stream in result.Contents.Where((content, idx) => result.IsStream(idx)))
{
var file = new FileData(stream.Headers.ContentDisposition.FileName);
var contentTest = stream.ReadAsByteArrayAsync();
// ... and so on, as per your original code.
}
return myParameter;
});
}
I tested it with the following HTML form:
<form action="/api/values" method="post" enctype="multipart/form-data">
<input name="myParameter" type="hidden" value="i dont do anything interesting"/>
<input type="file" name="file1" />
<input type="file" name="file2" />
<input type="submit" value="OK" />
</form>
I really needed the media type and length of the files uploaded so I modified #Mark Seefeldt answer slightly to the following:
public class MultipartFormFile
{
public string Name { get; set; }
public long? Length { get; set; }
public string MediaType { get; set; }
public Stream Stream { get; set; }
}
public class MultipartFormDataMemoryStreamProvider : MultipartMemoryStreamProvider
{
private readonly Collection<bool> _isFormData = new Collection<bool>();
private readonly NameValueCollection _formData = new NameValueCollection(StringComparer.OrdinalIgnoreCase);
private readonly List<MultipartFormFile> _fileStreams = new List<MultipartFormFile>();
public NameValueCollection FormData
{
get { return _formData; }
}
public List<MultipartFormFile> FileStreams
{
get { return _fileStreams; }
}
public override Stream GetStream(HttpContent parent, HttpContentHeaders headers)
{
if (parent == null)
{
throw new ArgumentNullException("parent");
}
if (headers == null)
{
throw new ArgumentNullException("headers");
}
var contentDisposition = headers.ContentDisposition;
if (contentDisposition == null)
{
throw new InvalidOperationException("Did not find required 'Content-Disposition' header field in MIME multipart body part.");
}
_isFormData.Add(String.IsNullOrEmpty(contentDisposition.FileName));
return base.GetStream(parent, headers);
}
public override async Task ExecutePostProcessingAsync()
{
for (var index = 0; index < Contents.Count; index++)
{
HttpContent formContent = Contents[index];
if (_isFormData[index])
{
// Field
string formFieldName = UnquoteToken(formContent.Headers.ContentDisposition.Name) ?? string.Empty;
string formFieldValue = await formContent.ReadAsStringAsync();
FormData.Add(formFieldName, formFieldValue);
}
else
{
// File
var file = new MultipartFormFile
{
Name = UnquoteToken(formContent.Headers.ContentDisposition.FileName),
Length = formContent.Headers.ContentLength,
MediaType = formContent.Headers.ContentType.MediaType,
Stream = await formContent.ReadAsStreamAsync()
};
FileStreams.Add(file);
}
}
}
private static string UnquoteToken(string token)
{
if (string.IsNullOrWhiteSpace(token))
{
return token;
}
if (token.StartsWith("\"", StringComparison.Ordinal) && token.EndsWith("\"", StringComparison.Ordinal) && token.Length > 1)
{
return token.Substring(1, token.Length - 2);
}
return token;
}
}
Ultimately, the following was what worked for me:
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
var filesReadToProvider = await Request.Content.ReadAsMultipartAsync(provider);
foreach (var file in provider.FileData)
{
var fileName = file.Headers.ContentDisposition.FileName.Replace("\"", string.Empty);
byte[] documentData;
documentData = File.ReadAllBytes(file.LocalFileName);
DAL.Document newRecord = new DAL.Document
{
PathologyRequestId = PathologyRequestId,
FileName = fileName,
DocumentData = documentData,
CreatedById = ApplicationSecurityDirector.CurrentUserGuid,
CreatedDate = DateTime.Now,
UpdatedById = ApplicationSecurityDirector.CurrentUserGuid,
UpdatedDate = DateTime.Now
};
context.Documents.Add(newRecord);
context.SaveChanges();
}

Categories