c# Send image from WPF to WebAPI - c#

I have a WebAPI 2.1 service (ASP.Net MVC 4) that receive and image and related data.
I need to send this image from WPF application, but I get 404 not found error.
Server side
[HttpPost]
[Route("api/StoreImage")]
public string StoreImage(string id, string tr, string image)
{
// Store image on server...
return "OK";
}
Client side
public bool SendData(decimal id, int time, byte[] image)
{
string url = "http://localhost:12345/api/StoreImage";
var wc = new WebClient();
wc.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
var parameters = new NameValueCollection()
{
{ "id", id.ToString() },
{ "tr", time.ToString() },
{ "image", Convert.ToBase64String(image) }
};
var res=wc.UploadValues(url, "POST", parameters);
return true;
}
The url exists, I thing I need to encode to json format, but I don't know how.
Thanks for your time!

The method parameters in your case are received in QueryString form.
I would suggest you turn the parameters list into one single object like this:
public class PhotoUploadRequest
{
public string id;
public string tr;
public string image;
}
Then in you API convert the string to buffer from Base64String like this:
var buffer = Convert.FromBase64String(request.image);
Then cast it to HttpPostedFileBase
HttpPostedFileBase objFile = (HttpPostedFileBase)new MemoryPostedFile(buffer);
Now you have the image file. Do whatever you want.
Full Code here:
[HttpPost]
[Route("api/StoreImage")]
public string StoreImage(PhotoUploadRequest request)
{
var buffer = Convert.FromBase64String(request.image);
HttpPostedFileBase objFile = (HttpPostedFileBase)new MemoryPostedFile(buffer);
//Do whatever you want with filename and its binaray data.
try
{
if (objFile != null && objFile.ContentLength > 0)
{
string path = "Set your desired path and file name";
objFile.SaveAs(path);
//Don't Forget to save path to DB
}
}
catch (Exception ex)
{
//HANDLE EXCEPTION
}
return "OK";
}
Edit:
I forgot to add the Code for MemoryPostedFile class
public class MemoryPostedFile : HttpPostedFileBase
{
private readonly byte[] fileBytes;
public MemoryPostedFile(byte[] fileBytes, string fileName = null)
{
this.fileBytes = fileBytes;
this.FileName = fileName;
this.InputStream = new MemoryStream(fileBytes);
}
public override void SaveAs(string filename)
{
File.WriteAllBytes(filename, fileBytes);
}
public override string ContentType => base.ContentType;
public override int ContentLength => fileBytes.Length;
public override string FileName { get; }
public override Stream InputStream { get; }
}

Related

Why Task.Run not processing all lines on Task?

Im using WebApi to Deserialize Object on client side, witch contains some lightweight images, the code reads:
private void Button_Click(object sender, object e)
{
LoadApi();
}
private async void LoadApi()
{
using (var client = new HttpClient())
{
var responseMessage = await client.GetAsync("http://" +
TxtIP.Text + "/api/prod");
if (responseMessage.StatusCode == System.Net.HttpStatusCode.OK)
{
List<ClsProd> lstData = new List<ClsProd>();
var jsonResponse = await
responseMessage.Content.ReadAsStringAsync();
if (jsonResponse != null)
{
lstData = Newtonsoft.Json.JsonConvert.DeserializeObject<List<ClsProd>>(jsonResponse);
}
ListView1.ItemsSource = lstData;
}
}
}
my ClsProd looks witch get all data from Web Api is:
public class ClsProd : System.ComponentModel.INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public int IAuto { get; set; }
public int IDevc { get; set; }
public string SName { get; set; }
public string SImax { get; set; }
public ImageSource ImgPg { get; set; }
public ClsProd(int auto, int devc, string name, string imax)
{
IAuto = auto;
IDevc = devc;
SName = name;
SImax = imax;
ClsImgBase64 CImg = new ClsImgBase64();
CImg.EvtResult += CImg_EvtResult;
CImg.Start(imax);
}
private void CImg_EvtResult(ImageSource e)
{
ImgPg = e;
NotifyPropertyChanged("ImgPg");
}
}
All data is properly fetch and displayed on list, including string SImax witch is image encoded as Base64 string. The only problem is image conversion from base64 string to image is not happening.
Here is my class it does not pass the 1st statment on Task.Run, please help me find what is wrong. Also same funcition works when called from async void.
public class ClsImgBase64
{
public event Action<ImageSource> EvtResult;
public ClsImgBase64()
{
}
public void Start(string s)
{
System.Threading.Tasks.Task.Run(async () =>
{
//read stream
byte[] bytes = Convert.FromBase64String(s);
var image = bytes.AsBuffer().AsStream().AsRandomAccessStream();
//decode image
//var decoder = await BitmapDecoder.CreateAsync(image);
image.Seek(0);
//create bitmap
var output = new WriteableBitmap(1, 1);
await output.SetSourceAsync(image);
if (EvtResult != null)
{
EvtResult(output);
}
});
}
}
As per async void there's probably an Exception thrown which was lost and not displayed bacause the executing code is not awaited. Let's fix it.
Web part
avoid async void in methods that's aren't event handlers, also handle all possible exceptions in async void method
HttpClient is intended to be instantiated once per app rather than per use
HttpResponseMessage is IDisposable
private async void Button_Click(object sender, object e)
{
try
{
await LoadDataAsync();
}
catch (Exception ex)
{
// show ex.Message here in UI or log it
}
}
private static readonly HttpClient _client = new HttpClient();
private async Task LoadDataAsync()
{
using var response = await _client.GetAsync($"http://{TxtIP.Text}/api/prod");
string json = await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync();
List<ClsProd> data = JsonConvert.DeserializeObject<List<ClsProd>>(json);
ListView1.ItemsSource = data;
await DecodeAllImagesAsync(data);
}
// decoding all at once asynchronously, see implementation below
private Task DecodeAllImagesAsync(List<ClsProd> data)
{
return Task.WhenAll(data.Select(item => item.DecodeImageAsync()).ToArray());
}
Consider using System.Text.Json to deserealize instead of old Newtonsoft.Json. It would allow to deserealize response.Content as Stream, faster with less memory consumption e.g:
using var stream = await response.EnsureSuccessStatusCode().Content.ReadAsStreamAsync();
List<ClsProd> data = await JsonSerializer.DeserealizeAsync<List<ClsProd>>(stream);
Data part
Use using directives at the beggining of the code to attach namespaces that will help not to repeat namespaces in the code explicitly
using System.ComponentModel;
It makes possible to write INotifyPropertyChanged instead of System.ComponentModel.INotifyPropertyChanged. I'll remove inlined namespaces below.
don't start long-running job from a constructor, it's unpredictable behavior because costructor must be always successful. Start loading images later. Also constructor cannot await asynchronous tasks. Separate method can.
public class ClsProd : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private ImageSource _imgPg;
public int IAuto { get; set; }
public int IDevc { get; set; }
public string SName { get; set; }
public string SImax { get; set; }
public ImageSource ImgPg
{
get => _imgPg;
set
{
_imgPg = value;
NotifyPropertyChanged();
}
}
public ClsProd(int auto, int devc, string name, string imax)
{
IAuto = auto;
IDevc = devc;
SName = name;
SImax = imax;
}
public async Task DecodeImageAsync()
{
ImgPg = await ClsImgBase64.DecodeAsync(SImax);
}
}
Decoder
As now it's awaitable and doesn't need a callback, decoding method doesn't interact with the instance data. So, it can be static.
public static class ClsImgBase64
{
public static async Task<ImageSource> DecodeAsync(string base64)
{
byte[] bytes = Convert.FromBase64String(base64);
using var stream = bytes.AsBuffer().AsStream().AsRandomAccessStream();
// stream.Seek(0); // not sure if it needed
var decoder = await BitmapDecoder.CreateAsync(stream);
var pixelData = await decoder.GetPixelDataAsync();
var pixelArray = pixelData.DetachPixelData();
var bitmap = new WriteableBitmap((int)decoder.PixelWidth, (int)decoder.PixelHeight);
await bitmap.PixelBuffer.AsStream().WriteAsync(pixelArray, 0, pixelArray.Length);
return bitmap;
}
}
Decoder's code based on this answer.
If it will be laggy, try to wrap 2 Decoder's lines with Task.Run. Only if it will be laggy.
using var stream = await Task.Run(() =>
{
byte[] bytes = Convert.FromBase64String(base64);
return bytes.AsBuffer().AsStream().AsRandomAccessStream();
});
Finally: give classes, methods and other things more clear names, that would make the code maintainable.

Download Blob as a file in ASP.NET MVC

I am saving a file to MySQL database as blob. Could you please help me, how can I download this blob as a file? I have blob data and ContentType in the database. You can see my method for downloading below. I have been searching for over a week, but I couldn't make it. I also don't know that I can download directly over method or I need to write ajax. I highly appreciate your help and assistance. Thanks a lot!
Method:
public HttpPostedFileBase Indir()
{
using (ISession session=FluentNHibernateHelper.OpenSession())
{
var doc = new Document();
var docDet = new DocumentDetail();
doc = session.Query<Document>().FirstOrDefault(x => x.Id == 5);
docDet = session.Query<DocumentDetail>().FirstOrDefault(x => x.DocumentId == doc.Id);
var test = new MemoryPostedFile(docDet.File, doc.DocumentName, doc.DocumentExtention);
return test;
}
}
Class:
public class MemoryPostedFile:HttpPostedFileBase
{
private readonly byte[] fileBytes;
public MemoryPostedFile(byte[] fileBytes, string fileName = null, string ContentType = null)
{
this.fileBytes = fileBytes;
this.FileName = fileName;
this.ContentType = ContentType;
this.InputStream = new MemoryStream(fileBytes);
}
public override int ContentLength => fileBytes.Length;
public override string FileName { get; }
public override string ContentType { get; }
public override Stream InputStream { get; }
}
Thank you so much for your answers! I found the solution and I am writing the code block below.
public HttpPostedFileBase Indir(string documentId)
{
using (ISession session=FluentNHibernateHelper.OpenSession())
{
var doc = new Document();
var docDet = new DocumentDetail();
doc = session.Query<Document>().FirstOrDefault(x => x.Id == Convert.ToInt32(documentId));
docDet = session.Query<DocumentDetail>().FirstOrDefault(x=>x.DocumentId==doc.Id);
var test = new MemoryPostedFile(docDet.File,doc.DocumentName,doc.DocumentExtention);
Response.Clear();
Response.ContentType = doc.DocumentExtention;//"application/octet-stream";
Response.AddHeader("Content-Disposition","attachment; filename="+test.FileName+";");
Response.BinaryWrite(docDet.File);
Server.MapPath("~/"+test.FileName);
Response.End();
return test;
}
}

An exception of type 'System.InvalidOperationException' occurred in System.Web.Extensions.dll but was not handled in user code

Why do have this error on this part of the code ?
this part
var data = serializer.Deserialize<EmailTemplate>(httpRequest.Form["data"].ToString());
my procedure
public int UpdateEmailTemplate()
{
HttpResponseMessage result = null;
ObjectService uploadFile = new ObjectService();
List<ActiveUp.Net.Mail.Attachment> attachment = new List<ActiveUp.Net.Mail.Attachment>();
var httpRequest = HttpContext.Current.Request;
if (httpRequest.Form["data"] != null)
{
var serializer = new JavaScriptSerializer();
var data = serializer.Deserialize<EmailTemplate>(httpRequest.Form["data"].ToString());
if (httpRequest.Files.Count > 0)
{
var docfiles = new List<string>();
foreach (string file in httpRequest.Files)
{
MemoryStream target = new MemoryStream();
httpRequest.Files[file].InputStream.CopyTo(target);
uploadFile.AddObject(data.Id, "SU_ENTITY_MSG_TEMPLATE","", target.GetBuffer(), httpRequest.Files[file].FileName);
}
}
AdminService List = new AdminService();
return List.UpdateEmailTemplate(data);
}
MY class
public class EmailTemplate
{
public int Id;
public string TypeObject;
public int? idObject;
public string ObjectName;
public string IdTeam;
public string IdTask;
public string Team;
public string task;
public string Title;
public string Subject;
public string dataHtml;
public List<Objects> fileListRequest;
}
It's pretty plainly saying that the contents of:
httpRequest.Form["data"]
...is an array of some sort. Adding .ToString() will just return it's type name. Passing that to Deserialize is what's giving you the error, as Deserialize is expecting an array and just getting a string.
Try taling the .ToString() off - this may not work. If it doesn't you'll have to convert httpRequest.Form["data"] to what ever data type you've put into it (whuich you do not say).

how to upload a large file with ASP.NET MVC4 Web Api with progressbar

how can i upload a large file with ASP.NET MVC4 Web Api
and also get a progress?
i saw this post and i understand how to handle the uploaded file but how i can get the progress data?
How To Accept a File POST
please don't send me links to upload products.
i want to understand how handle this in the MVC4 Web Api way...
here is an example code of handling a file upload in MVC4 WebApi
public async Task<HttpResponseMessage> Post()
{
if (Request.Content.IsMimeMultipartContent())
{
var path = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(path);
await Request.Content.ReadAsMultipartAsync(provider).ContinueWith(t =>
{
if (t.IsFaulted || t.IsCanceled)
throw new HttpResponseException(HttpStatusCode.InternalServerError);
});
return Request.CreateResponse(HttpStatusCode.OK);
}
else
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "This request is not properly formatted"));
}
}
now when
await Request.Content.ReadAsMultipartAsync(provider)
how can i get how bytes loaded?
There is a limitation to the size of files to be uploaded by default at two places. One at the request level, and second , if you hosting on IIS, then on web server level. I added couple of configs as mentioned in this blog, and i was able to upload a 36mb file without any issues. I have posted the snippet below.
Basically
1.
<system.web>
<httpRuntime maxRequestLength="2097152"/>
</system.web>
2.
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="2147483648" />
</requestFiltering>
</security><system.webServer>
Its easy to find the size of the file loaded into the server if you wish. In your code
while reading through the filedata in the stream, for each item in you file data, you can read the local file name as shown below.
string savedFile = fileData.LocalFileName;
// use the file info class to derive properties of the uploaded file
FileInfo file = new FileInfo(savedFile);
//this will give the size of the uploaded file
long size = file.length/1024
Hope this helps. I wonder why this was marked down?
I use this solution:
public class UploadController : ApiController
{
private static ConcurrentDictionary<string, State> _state = new ConcurrentDictionary<string, State>();
public State Get(string id)
{
State state;
if (_state.TryGetValue(id, out state))
{
return state;
}
return null;
}
public async Task<HttpResponseMessage> Post([FromUri] string id)
{
if (Request.Content.IsMimeMultipartContent())
{
var state = new State(Request.Content.Headers.ContentLength);
if (!_state.TryAdd(id, state))
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Conflict));
var path = System.Web.Hosting.HostingEnvironment.MapPath("~/App_Data");
var provider = new FileMultipartStreamProvider(path, state.Start, state.AddBytes);
await Request.Content.ReadAsMultipartAsync(provider).ContinueWith(t =>
{
_state.TryRemove(id, out state);
if (t.IsFaulted || t.IsCanceled)
throw new HttpResponseException(HttpStatusCode.InternalServerError);
});
return Request.CreateResponse(HttpStatusCode.OK);
}
else
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "This request is not properly formatted"));
}
}
}
public class State
{
public long? Total { get; set; }
public long Received { get; set; }
public string Name { get; set; }
public State(long? total = null)
{
Total = total;
}
public void Start(string name)
{
Received = 0;
Name = name;
}
public void AddBytes(long size)
{
Received = size;
}
}
public class FileMultipartStreamProvider : MultipartStreamProvider
{
private string _rootPath;
private Action<string> _startUpload;
private Action<long> _uploadProgress;
public FileMultipartStreamProvider(string root_path, Action<string> start_upload, Action<long> upload_progress)
: base()
{
_rootPath = root_path;
_startUpload = start_upload;
_uploadProgress = upload_progress;
}
public override System.IO.Stream GetStream(HttpContent parent, System.Net.Http.Headers.HttpContentHeaders headers)
{
var name = (headers.ContentDisposition.Name ?? "undefined").Replace("\"", "").Replace("\\", "_").Replace("/", "_").Replace("..", "_");
_startUpload(name);
return new WriteFileStreamProxy(Path.Combine(_rootPath, name), _uploadProgress);
}
}
public class WriteFileStreamProxy : FileStream
{
private Action<long> _writeBytes;
public WriteFileStreamProxy(string file_path, Action<long> write_bytes)
: base(file_path, FileMode.Create, FileAccess.Write)
{
_writeBytes = write_bytes;
}
public override void EndWrite(IAsyncResult asyncResult)
{
base.EndWrite(asyncResult);
#if DEBUG
System.Threading.Thread.Sleep(100);
#endif
if (_writeBytes != null)
_writeBytes(base.Position);
}
public override void Write(byte[] array, int offset, int count)
{
base.Write(array, offset, count);
#if DEBUG
System.Threading.Thread.Sleep(100);
#endif
if (_writeBytes != null)
_writeBytes(base.Position);
}
}
and small configure for non-buffered input stream:
config.Services.Replace(typeof(IHostBufferPolicySelector), new CustomPolicy());
implemented this:
public class CustomPolicy : System.Web.Http.WebHost.WebHostBufferPolicySelector
{
public override bool UseBufferedInputStream(object hostContext)
{
return false;
}
}
I Ended Up using an HttpModule but even the HttpModule won't show the progress bar
I found out something very interesting it's seems that when i upload the file in a Secure Protocol(over https://) then the progress are working but in non secure protocl (http://) the progress is not working and the file is fully buffered i don't know way is like that i believe it's a bug somewhere between the IIS to Asp.net Framework when the Request are get Procced.
now because i success make it work over https with an HttpModule i believe it is possible to make it work also with Mvc Web Api but i currently don't have the time to check that.
for parsing Mutlipart form data i used Nancy HttpMultipart parser here:
https://github.com/NancyFx/Nancy/tree/master/src/Nancy
just grabbed the classes:
HttpMultipart.cs
HttpMultipartBoundary.cs
HttpMultipartBuffer.cs
HttpMultipartSubStream.cs
here is the HttpModule Source:
public class HttpUploadModule : IHttpModule
{
public static DateTime lastClean = DateTime.UtcNow;
public static TimeSpan cleanInterval = new TimeSpan(0,10,0);
public static readonly object cleanLocker = new object();
public static readonly Dictionary<Guid,UploadData> Uploads = new Dictionary<Guid,UploadData>();
public const int KB = 1024;
public const int MB = KB * 1024;
public static void CleanUnusedResources( HttpContext context)
{
if( lastClean.Add( cleanInterval ) < DateTime.UtcNow ) {
lock( cleanLocker )
{
if( lastClean.Add( cleanInterval ) < DateTime.UtcNow )
{
int maxAge = int.Parse(ConfigurationManager.AppSettings["HttpUploadModule.MaxAge"]);
Uploads.Where(u=> DateTime.UtcNow.AddSeconds(maxAge) > u.Value.createdDate ).ToList().ForEach(u=>{
Uploads.Remove(u.Key);
});
Directory.GetFiles(context.Server.MapPath(ConfigurationManager.AppSettings["HttpUploadModule.Folder"].TrimEnd('/'))).ToList().ForEach(f=>{
if( DateTime.UtcNow.AddSeconds(maxAge) > File.GetCreationTimeUtc(f)) File.Delete(f);
});
lastClean = DateTime.UtcNow;
}
}
}
}
public void Dispose()
{
}
public void Init(HttpApplication app)
{
app.BeginRequest += app_BeginRequest;
}
void app_BeginRequest(object sender, EventArgs e)
{
HttpContext context = ((HttpApplication)sender).Context;
Guid uploadId = Guid.Empty;
if (context.Request.HttpMethod == "POST" && context.Request.ContentType.ToLower().StartsWith("multipart/form-data"))
{
IServiceProvider provider = (IServiceProvider)context;
HttpWorkerRequest wr = (HttpWorkerRequest)provider.GetService(typeof(HttpWorkerRequest));
FileStream fs = null;
MemoryStream ms = null;
CleanUnusedResources(context);
string contentType = wr.GetKnownRequestHeader(HttpWorkerRequest.HeaderContentType);
NameValueCollection queryString = HttpUtility.ParseQueryString( wr.GetQueryString() );
UploadData upload = new UploadData { id = uploadId ,status = 0, createdDate = DateTime.UtcNow };
if(
!contentType.Contains("boundary=") ||
/*AT LAST 1KB */ context.Request.ContentLength < KB ||
/*MAX 5MB */ context.Request.ContentLength > MB*5 ||
/*IS UPLOADID */ !Guid.TryParse(queryString["upload_id"], out uploadId) || Uploads.ContainsKey( uploadId )) {
upload.id = uploadId;
upload.status = 2;
Uploads.Add(upload.id, upload);
context.Response.StatusCode = 400;
context.Response.StatusDescription = "Bad Request";
context.Response.End();
}
string boundary = Nancy.HttpMultipart.ExtractBoundary( contentType );
upload.id = uploadId;
upload.status = 0;
Uploads.Add(upload.id, upload);
try {
if (wr.HasEntityBody())
{
upload.bytesRemaining =
upload.bytesTotal = wr.GetTotalEntityBodyLength();
upload.bytesLoaded =
upload.BytesReceived = wr.GetPreloadedEntityBodyLength();
if (!wr.IsEntireEntityBodyIsPreloaded())
{
byte[] buffer = new byte[KB * 8];
int readSize = buffer.Length;
ms = new MemoryStream();
//fs = new FileStream(context.Server.MapPath(ConfigurationManager.AppSettings["HttpUploadModule.Folder"].TrimEnd('/')+'/' + uploadId.ToString()), FileMode.CreateNew);
while (upload.bytesRemaining > 0)
{
upload.BytesReceived = wr.ReadEntityBody(buffer, 0, readSize);
if(upload.bytesRemaining == upload.bytesTotal) {
}
ms.Write(buffer, 0, upload.BytesReceived);
upload.bytesLoaded += upload.BytesReceived;
upload.bytesRemaining -= upload.BytesReceived;
if (readSize > upload.bytesRemaining)
{
readSize = upload.bytesRemaining;
}
}
//fs.Flush();
//fs.Close();
ms.Position = 0;
//the file is in our hands
Nancy.HttpMultipart multipart = new Nancy.HttpMultipart(ms, boundary);
foreach( Nancy.HttpMultipartBoundary b in multipart.GetBoundaries()) {
if(b.Name == "data") {
upload.filename = uploadId.ToString()+Path.GetExtension( b.Filename ).ToLower();
fs = new FileStream(context.Server.MapPath(ConfigurationManager.AppSettings["HttpUploadModule.Folder"].TrimEnd('/')+'/' + upload.filename ), FileMode.CreateNew);
b.Value.CopyTo(fs);
fs.Flush();
fs.Close();
upload.status = 1;
context.Response.StatusCode = 200;
context.Response.StatusDescription = "OK";
context.Response.Write( context.Request.ApplicationPath.TrimEnd('/') + "/images/temp/" + upload.filename );
}
}
}
}
}
catch(Exception ex) {
upload.ex = ex;
}
if(upload.status != 1)
{
upload.status = 2;
context.Response.StatusCode = 400;
context.Response.StatusDescription = "Bad Request";
}
context.Response.End();
}
}
}
public class UploadData {
public Guid id { get;set; }
public string filename {get;set;}
public int bytesLoaded { get; set; }
public int bytesTotal { get; set; }
public int BytesReceived {get; set;}
public int bytesRemaining { get;set; }
public int status { get;set; }
public Exception ex { get;set; }
public DateTime createdDate { get;set; }
}

Read an Excel spreadsheet in memory

How can I read an Excel spreadsheet that was just posted to my server?
I searched for something but I only found how to read an Excel spreadsheet with the file name path which is not my case.
I need something like that:
public ActionResult Import(HttpPostedFileBase file)
{
var excel = new ExcelQueryFactory(file); //using linq to excel
}
I was running into your same issue but I didn't want to switch to a paid service so this is what I did.
public class DataImportHelper : IDisposable
{
private readonly string _fileName;
private readonly string _tempFilePath;
public DataImportHelper(HttpPostedFileBase file, string tempFilePath)
{
_fileName = file.FileName;
_tempFilePath = Path.Combine(tempFilePath, _fileName);
(new FileInfo(_tempFilePath)).Directory.Create();
file.SaveAs(_tempFilePath);
}
public IQueryable<T> All<T>(string sheetName = "")
{
if (string.IsNullOrEmpty(sheetName))
{
sheetName = (typeof (T)).Name;
}
var excelSheet = new ExcelQueryFactory(_tempFilePath);
return from t in excelSheet.Worksheet<T>(sheetName)
select t;
}
public void Dispose()
{
File.Delete(_tempFilePath);
}
}
Here is a Test
[Fact]
public void AcceptsAMemoryStream()
{
MemoryFile file;
using (var f = File.OpenRead("SampleData.xlsx"))
{
file = new MemoryFile(f, "multipart/form-data", "SampleData.xlsx");
using (var importer = new DataImportHelper(file, "Temp/"))
{
var products = importer.All<Product>();
Assert.NotEmpty(products);
}
}
}
Here is MemoryFile.cs. This file is only used for testing. It is just an implementation of HttpPostedFileBase so you can test your controllers and my little helper. This was borrowed from another post.
public class MemoryFile : HttpPostedFileBase
{
Stream stream;
string contentType;
string fileName;
public MemoryFile(Stream stream, string contentType, string fileName)
{
this.stream = stream;
this.contentType = contentType;
this.fileName = fileName;
}
public override int ContentLength
{
get { return (int)stream.Length; }
}
public override string ContentType
{
get { return contentType; }
}
public override string FileName
{
get { return fileName; }
}
public override Stream InputStream
{
get { return stream; }
}
public override void SaveAs(string filename)
{
using (var file = File.Open(filename, FileMode.Create))
stream.CopyTo(file);
}
}
Unfortunately it's not possible to read a spreadsheet from a stream with LinqToExcel.
That's because it uses OLEDB to read from the spreadsheets and it can't read from a stream.
You can use the InputStream property of HttpPostedFileBase to read the excel spreadsheet in memory.
I use ClosedXML nuget package to read excel content from stream which is available in your case. It has a simple overload which takes stream pointing to stream for the excel file (aka workbook).
imported namespaces at the top of the code file:
using ClosedXML.Excel;
Source code:
public ActionResult Import(HttpPostedFileBase file)
{
//HttpPostedFileBase directly is of no use so commented your code
//var excel = new ExcelQueryFactory(file); //using linq to excel
var stream = file.InputStream;
if (stream.Length != 0)
{
//handle the stream here
using (XLWorkbook excelWorkbook = new XLWorkbook(stream))
{
var name = excelWorkbook.Worksheet(1).Name;
//do more things whatever you like as you now have a handle to the entire workbook.
var firstRow = excelWorkbook.Worksheet(1).Row(1);
}
}
}
You need Office Interops assemblies. Check the Excel Object Model for reference.

Categories