Upload and read excel file using .NET core MVC - entity-framework

I have implemented an algorithm of reading an excel file using NPOI in .NET Core. Now I want is to upload a excel file in a web-app and read the excel file during uploading time and save it to database.
I am a bit confused about how should I approach for Model, Views and Controllers. Here are the optimized requirement list:
To ensure user is uploading .xlsx file (not other files)
Reading excel data during upload time
Save data in a database

Your controller can be something like this:
public ActionResult Import(System.Web.HttpPostedFileBase uploadFile)
{
if (uploadFile != null)
{
if (uploadFile.ContentLength > 0)
{
var fileExtension = Path.GetExtension(uploadFile.FileName);
if (fileExtension.ToLower().Equals(".xlsx"))
{
BinaryReader b = new BinaryReader(uploadFile.InputStream);
int count = uploadFile.ContentLength;
byte[] binData = b.ReadBytes(count);
using (MemoryStream stream = new MemoryStream(binData))
{
//call the service layer to read the data from stream
}
}
}
}
}
And your service layer is what you already figured out, using NPOI to read.
Based on the data you are reading from the excel file, your model could be something like this:
public class Product
{
public int ProductID {get; set;}
public string Name {get; set;}
public decimal Price {get; set;}
}
In the database, you can have a stored procedure which can take multiple rows of data using user-defined table type. You can call this stored procedure from your repository, after your read the data in service layer.
Finally in the view, you can have a form with the file upload dialog and pass the file uploaded by the user. Javascript to call the controller could be something like this:
function x () {
var inputdata = null;
var form = $('#__ImportFileForm');
var token = $('input[name="__RequestVerificationToken"]', form).val();
var fd = new FormData();
fd.append("__RequestVerificationToken", token);
fd.append("uploadFile", $("#uploadFile")[0].files[0]);
inputdata = fd;
inputdata.skipAjaxPrefilter = true;
$.ajax({
type: "Post",
url: url,
data: inputdata,
processData: false,
contentType: false,
traditional: true,
async: true,
success: function(data) { //do what you want },
error: function(data, ajaxOptions, thrownError) { //do what you want }
});
}
Hope this answers all your questions!

Related

Image not displaying in .NET MAUI Mac Catalyst

I'm trying to display an image to make my own custom splash screen using .NET MAUI. It is made in C# and does not use XAML. Here is my code:
SplashScreenActivity.cs:
using System;
using System.Text;
namespace LiveEditorHTML
{
public partial class SplashScreenActivity : ContentPage
{
Image splashScreenImage;
public async Task<string> ShowMsg(string title,
string msg, bool isQuestion, bool isInput,
int? num, string[]? question)
{
bool answer;
if (isQuestion && !isInput)
{
answer = await DisplayAlert(title, msg, "Yes", "No");
return answer.ToString();
}
else if (!isQuestion && !isInput)
{
await DisplayAlert(title, msg, "OK");
}
else if (!isQuestion && isInput)
{
string action = await DisplayActionSheet(
title + msg, "Cancel",
null, question
);
}
else
{
await DisplayAlert(title, msg, "OK");
}
return null;
}
public SplashScreenActivity()
{
var uiView = new ScrollView();
var stackLayout = new VerticalStackLayout();
var img = AssetsHelper.LoadMauiAsset("logo.png").Result;
Task.Run(() =>
{
string output = ShowMsg("Debug", img, false, false, 0, null).Result;
});
byte[] byteArray = Encoding.UTF8.GetBytes(img);
MemoryStream stream = new MemoryStream(byteArray);
splashScreenImage = new Image()
{
Source = ImageSource.FromStream(() => stream)
};
stackLayout.Children.Add(splashScreenImage);
this.Content = uiView;
}
}
}
AssetsHelper.cs:
using System;
namespace LiveEditorHTML
{
public class AssetsHelper
{
public static async Task<string> LoadMauiAsset(string fileName)
{
using var stream = await FileSystem.OpenAppPackageFileAsync(fileName);
using var reader = new StreamReader(stream);
var contents = reader.ReadToEndAsync();
return contents.Result;
}
}
}
Here is the image I used, created with GIMP, the image size is 456x456 (the same as the image sizes of the appicon.svg and appiconfg.svg files located at: Resources\AppIcon):
The ShowMsg() function is used to create a MessageBox like C# Winforms, in addition, it is also used for the cases of creating a Yes No questionnaire, and creating a questionnaire that requires the user to enter text. Currently, I just use the simplest feature, like the MessageBox in C# Winforms, which is to print a debug message, with the title Debug and the content as an image file that is read with the help of the AssetsHelper.cs support class.
When I run the program, the Debug window for printing information pops up, indicating that it is working, the file logo.png (with path at Resources\Raw) has been read successfully:
But then nothing is displayed:
I highly suspected that there might be an error, but no exceptions occurred, so I used the built-in image: dotnet_bot.svg to test (link at: Resources\Images). I replaced the following line in SplashScreenActivity.cs:
ImageSource.FromStream(() => stream)
Fort:
"dotnet_bot.svg"
The code becomes:
splashScreenImage = new Image()
{
Source = "dotnet_bot.svg"
};
to test. When I turn on the app and go through the Debug screen (since I haven't dropped the code that shows the Debug dialog), they don't work either:
And no exception is thrown. The app doesn't crash either.
All versions of .NET MAUI and VS are updated and VS is the latest Preview version. The computer I'm using is a Macbook Pro running macOS Monterey 12.5.1
So what's going on?
I had create a sample to test your code and the image can't show either. I found that you have changed the image file to the string, and then changed the string to the byte array.
You can try to convert the image to the byte array or set the image as the source directly. In addition, you didn't add the stacklayout into the scrollview. So you should add it or set the stacklayout as the page.content.
Set the image as the source directly
splashScreenImage = new Image()
{
Source = "test" // the test.png is in the /Resource/Image folder
};
stackLayout.Children.Add(splashScreenImage);
this.Content = stackLayout;
2.Convert the image to the byte array directly
public static async Task<byte[]> LoadMauiAsset(string fileName)
{
using var stream = await FileSystem.OpenAppPackageFileAsync(fileName);
byte[] buffer = new byte[stream.Length];
stream.Read(buffer, 0, buffer.Length);
return buffer;
}
MemoryStream stream = new MemoryStream(AssetsHelper.LoadMauiAsset("test").Result);
splashScreenImage = new Image()
{
Source = ImageSource.FromStream(() => stream)
};
stackLayout.Children.Add(splashScreenImage);
this.Content = stackLayout;

storing object in cosmos db returns bad request?

I seem to be unable to store a simple object to cosmos db?
this is the database model.
public class HbModel
{
public Guid id { get; set; }
public string FormName { get; set; }
public Dictionary<string, object> Form { get; set; }
}
and this is how I store the data into the database
private static void SeedData(HbModelContext dbContext)
{
var cosmosClient = dbContext.Database.GetCosmosClient();
cosmosClient.ClientOptions.AllowBulkExecution = true;
if (dbContext.Set<HbModel>().FirstOrDefault() == null)
{
// No items could be picked hence try seeding.
var container = cosmosClient.GetContainer("hb", "hb_forms");
HbModel first = new HbModel()
{
Id = Guid.NewGuid(),//Guid.Parse(x["guid"] as string),
FormName = "asda",//x["name"] as string,
Form = new Dictionary<string, object>() //
}
string partitionKey = await GetPartitionKey(container.Database, container.Id);
var response = await container.CreateItemAsync(first, new PartitionKey(partitionKey));
}
else
{
Console.WriteLine("Already have data");
}
}
private static async Task<string> GetPartitionKey(Database database, string containerName)
{
var query = new QueryDefinition("select * from c where c.id = #id")
.WithParameter("#id", containerName);
using var iterator = database.GetContainerQueryIterator<ContainerProperties>(query);
while (iterator.HasMoreResults)
{
foreach (var container in await iterator.ReadNextAsync())
{
return container.PartitionKeyPath;
}
}
return null;
}
but when creating the item I get this error message
A host error has occurred during startup operation '3b06df1f-000c-4223-a374-ca1dc48d59d1'.
[2022-07-11T15:02:12.071Z] Microsoft.Azure.Cosmos.Client: Response status code does not indicate success: BadRequest (400); Substatus: 0; ActivityId: 24bac0ba-f1f7-411f-bc57-3f91110c4528; Reason: ();.
Value cannot be null. (Parameter 'provider')
no idea why it fails?
the data should not be formatted incorreclty?
It also fails in case there is data in the dictionary.
What is going wrong?
There are several things wrong with the attached code.
You are enabling Bulk but you are not following the Bulk pattern
cosmosClient.ClientOptions.AllowBulkExecution = true is being set, but you are not parallelizing work. If you are going to use Bulk, make sure you are following the documentation and creating lists of concurrent Tasks. Reference: https://learn.microsoft.com/azure/cosmos-db/sql/tutorial-sql-api-dotnet-bulk-import#step-6-populate-a-list-of-concurrent-tasks. Otherwise don't use Bulk.
You are blocking threads.
The call to container.CreateItemAsync(first, new PartitionKey("/__partitionKey")).Result; is a blocking call, this can lead you to deadlocks. When using async operations (such as CreateItemAsync) please use the async/await pattern. Reference: https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md#avoid-using-taskresult-and-taskwait
The PartitionKey parameter should be the value not the definition.
On the call container.CreateItemAsync(first, new PartitionKey("/__partitionKey")) the Partition Key (second parameter) should be the value. Assuming your container has a Partition Key Definition of /__partitionKey then your documents should have a __partitionKey property and you should pass the Value in this parameter of such property in the current document. Reference: https://learn.microsoft.com/azure/cosmos-db/sql/troubleshoot-bad-request#wrong-partition-key-value
Optionally, if your documents do not contain such a value, just remove the parameter from the call:
container.CreateItemAsync(first)
Be advised though that this solution will not scale, you need to design your database with Partitioning in mind: https://learn.microsoft.com/azure/cosmos-db/partitioning-overview#choose-partitionkey
Missing id
The model has Id but Cosmos DB requires id, make sure the content of the document contains id when serialized.

How to provide a friendly name to a File in ASP.NET Core without forcing the file to be downloaded?

I have the following REST Endpoint defined in my ASP.NET Core 3.1 application:
[HttpGet("files/{id}")]
[ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GetFile(int id)
{
// ...
return File(stream, mime);
}
If I leave the code as-is, then the file is either immediately downloaded or previewed in the browser depending on whether or not the browser can preview the file (i.e. pdf file). However, when the user goes to download the file, the name of the file is the id; for example, saving the pdf will suggest to save 701.pdf. Files which cannot be previewed are immediately downloaded with that same convention.
I can supply the downloadFileName return File(stream, mime, friendlyName), but then even files which could be previewed (i.e. pdf files) are immediately downloaded. Is there a way to provide a friendly name without enforcing file download?
Try this two workaround:
1)
View:
<a asp-action="GetFile" asp-controller="Users">Download</a>
Controller (be sure that the file have been exsit in wwwroot/file folder):
[HttpGet]
public ActionResult GetFile()
{
string filePath = "~/file/test.pdf";
Response.Headers.Add("Content-Disposition", "inline; filename=test.pdf");
return File(filePath, "application/pdf");
}
Startup.cs:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//...
app.UseStaticFiles();
//...
}
public async Task<IActionResult> GetFile()
{
var path = Path.Combine(
Directory.GetCurrentDirectory(), "wwwroot\\images\\4.pdf");
var memory = new MemoryStream();
using (var stream = new FileStream(path, FileMode.Open))
{
await stream.CopyToAsync(memory);
}
memory.Position = 0;
return File(memory, "application/pdf", "Demo.pdf");
}
View:
<form asp-controller="pdf" asp-action="GetFile" method="get">
<button type="submit">Download</button>
</form>

HTTP Put is not working for me in MVC 4 Web API. I keep getting a 404

var assetimage_id = $(this).closest(".assetImageWrapper").attr("data-assetimage_id");
var dataToSend = JSON.stringify({ "Asset_ID": assetimage_id, "Description": $(this).val() });
$.ajax({
url: "/api/Assets/UpdateDescription",
type: "PUT",
contentType: "application/json; charset=utf-8",
dataType: "json",
data: dataToSend,
success: function (data) {
alert("success");
}
});
And here is the method that it is supposed to hit.
[HttpPut]
public Asset UpdateDescription(int Asset_ID, string Description)
{
return new AssetsService().UpdateAssetDescription(Asset_ID, Description);
}
What looks out of whack? The method is setting in a Web API controller called Assets. All other methods work fine from it (GETS, POSTS). This is when I'm running it by hitting F5 in Visual Studio 2012, so no IIS configuration was changed. the Api route is what the default route is.
And my web.config supports all verbs:
by default, simple types like 'Asset_ID' above and also string 'Description' are binded from Uri. In you scenario you seem to sending the content in the body, so you would need to change your api signature accordingly. BTW, you also cannot have multiple FromBody parameters on your action.
For a complex model, you need a view model created to contain them:
Once created:
public class AssetEstimatedValueUpdate
{
public int Asset_ID { get; set; }
public string EstimatedValue { get; set; }
}
Then you can pass it in and all works well.
[HttpPut]
public Asset UpdateDescription(AssetDescriptionUpdate _AssetDescriptionUpdate)
{
return new AssetsService().UpdateAssetDescription(_AssetDescriptionUpdate.Asset_ID, _AssetDescriptionUpdate.Description);
}

Chain multiple 2 step file uploads with Rx

I am attempting to upload multiple files from a Silverlight client directly to Amazon S3. The user chooses the files from the standard file open dialog and I want to chain the uploads so they happen serially one at a time. This can happen from multiple places in the app so I was trying to wrap it up in a nice utility class that accepts an IEnumerable of the chosen files exposes an IObservable of the files as they are uploaded so that the UI can respond accordingly as each file is finished.
It is fairly complex due to all the security requirements of both Silverlight and AmazonS3. I'll try to briefly explain my whole environment for context, but I have reproduced the issue with a small console application that I will post the code to below.
I have a 3rd party utility that handles uploading to S3 from Silverlight that exposes standard event based async methods. I create one instance of that utility per uploaded file. It creates an unsigned request string that I then post to my server to sign with my private key. That signing request happens through a service proxy class that also uses event based async methods. Once I have the signed request, I add it to the uploader instance and initiate the upload.
I've tried using Concat, but I end up with only the first file going through the process. When I use Merge, all files complete fine, but in a parallel fashion rather than serially. When I use Merge(2) all files start the first step, but then only 2 make their way through and complete.
Obviously I am missing something related to Rx since it isn't behaving like I expect.
namespace RxConcat
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Timers;
public class SignCompletedEventArgs : EventArgs
{
public string SignedRequest { get; set; }
}
public class ChainUploader
{
public IObservable<string> StartUploading(IEnumerable<string> files)
{
return files.Select(
file => from signArgs in this.Sign(file + "_request")
from uploadArgs in this.Upload(file, signArgs.EventArgs.SignedRequest)
select file).Concat();
}
private IObservable<System.Reactive.EventPattern<SignCompletedEventArgs>> Sign(string request)
{
Console.WriteLine("Signing request '" + request + "'");
var signer = new Signer();
var source = Observable.FromEventPattern<SignCompletedEventArgs>(ev => signer.SignCompleted += ev, ev => signer.SignCompleted -= ev);
signer.SignAsync(request);
return source;
}
private IObservable<System.Reactive.EventPattern<EventArgs>> Upload(string file, string signedRequest)
{
Console.WriteLine("Uploading file '" + file + "'");
var uploader = new Uploader();
var source = Observable.FromEventPattern<EventArgs>(ev => uploader.UploadCompleted += ev, ev => uploader.UploadCompleted -= ev);
uploader.UploadAsync(file, signedRequest);
return source;
}
}
public class Signer
{
public event EventHandler<SignCompletedEventArgs> SignCompleted;
public void SignAsync(string request)
{
var timer = new Timer(1000);
timer.Elapsed += (sender, args) =>
{
timer.Stop();
if (this.SignCompleted == null)
{
return;
}
this.SignCompleted(this, new SignCompletedEventArgs { SignedRequest = request + "signed" });
};
timer.Start();
}
}
public class Uploader
{
public event EventHandler<EventArgs> UploadCompleted;
public void UploadAsync(string file, string signedRequest)
{
var timer = new Timer(1000);
timer.Elapsed += (sender, args) =>
{
timer.Stop();
if (this.UploadCompleted == null)
{
return;
}
this.UploadCompleted(this, new EventArgs());
};
timer.Start();
}
}
internal class Program
{
private static void Main(string[] args)
{
var files = new[] { "foo", "bar", "baz" };
var uploader = new ChainUploader();
var token = uploader.StartUploading(files).Subscribe(file => Console.WriteLine("Upload completed for '" + file + "'"));
Console.ReadLine();
}
}
}
The base observable that is handling the 2 step upload for each file is never 'completing' which prevents the next one in the chain from starting. Add a Limit(1) to that observable prior to calling Concat() and it will working correctly.
return files.Select(file => (from signArgs in this.Sign(file + "_request")
from uploadArgs in this.Upload(file, signArgs.EventArgs.SignedRequest)
select file).Take(1)).Concat();