Trying to serve files that are in a byte array to my details view in an ASP.NET Core 2 MVC web app - asp.net

Currently I have a database with two tables, one table is for products and the other is for file attachments for these products. hen I edit my product, I have an upload form to attach a file. It is uploading to the database fine.
What I need help with is how to serve those files back to the details view of the product as links so they can download them.
Here is my model for the file attachment.
namespace Product.Models
{
public class FileAttachments
{
public int ID { get; set; }
public int ProductID { get; set; }
public byte[] Attachments { get; set; }
public string MimeType { get; set; }
public string FileName { get; set; }
}
}
ProductID is my key for my main table
Here is my upload method
[HttpPost]
public IActionResult Upload(ICollection<IFormFile> files, int ID)
{
foreach (var f in files)
{
FileAttachments NewFile = new FileAttachments();
var ms = new MemoryStream();
f.CopyTo(ms);
NewFile.Attachments = ms.ToArray();
NewFile.ProductID = ID;
NewFile.FileName = f.FileName;
NewFile.MimeType = f.ContentType;
_db.Attach(NewFile);
}
_db.SaveChanges();
return Redirect(Request.Headers["Referer"].ToString());
}

Related

How to send a collection of files + JSON data to the client side

In my ASP.NET Core Web API I have an entity Idea:
public class Idea
{
public int Id { get; set; }
public string Name { get; set; }
public string OwnerId { get; set; }
public User Owner { get; set; }
public string Description { get; set; }
public int? MainPhotoId { get; set; }
public Photo MainPhoto { get; set; }
public int? MainVideoId { get; set; }
public Video MainVideo { get; set; }
public ICollection<Photo> Photos { get; set; }
public ICollection<Video> Videos { get; set; }
public ICollection<Audio> Audios { get; set; }
public ICollection<Document> DocumentsAboutTheIdea { get; set; }
public DateTime DateOfPublishing { get; set; }
}
public class Photo
{
public int Id { get; set; }
public string Url { get; set; }
}
(the different media-type classes are equivalent)
When the client makes a Post request for creating the Idea he sends the information about it along with all the media-files (I am using IFormFile and IFormFileCollection) and while saving them on the server I set the Url property to match their location. But in the Get request I want to send the files (not Urls).
Here is the Get action which now returns only one file (mainPhoto) without any JSON and without any other media-files:
[HttpGet("{id}", Name = "Get")]
public async Task<IActionResult> Get(int id)
{
var query = await unitOfWork.IdeaRepository.GetByIdAsync(id, includeProperties: "Owner,MainPhoto,MainVideo,Photos,Videos,Audios,DocumentsAboutTheIdea");
if (query != null)
{
string webRootPath = hostingEnvironment.WebRootPath;
var path = string.Concat(webRootPath, query.MainPhoto.Url);
var memory = new MemoryStream();
using (var stream = new FileStream(path, FileMode.Open))
{
await stream.CopyToAsync(memory);
}
memory.Position = 0;
return File(memory, GetContentType(path), Path.GetFileName(path));
}
return NotFound();
}
So in a Get request I want to send to the client side (an Angular app) some of the information about the Idea in a JSON-format ALONG WITH different (NOT ONE) media-files associated with it. My goal is to make the client side get it all so that it can then show them on the page of the info about the Idea. But I cannot figure out the approach for this. Is there a way to achieve this? Or there is another (better) way of interacting with the client side to transfer all the required data? I tried to find information on this topic but couldn't find any.

How to update table using query parameter in Html Agility Pack using C#

I've been parsing this Url with Html Agility Pack:
http://www.cmegroup.com/trading/energy/crude-oil/light-sweet-crude_quotes_settlements_options.html"
The default table displayed is always the closest contract date and the current date.
I have no problem parsing the complete page above but if I ask for another date I can't seem to get a new table when I add a query parameter to get another date:
eg. http://www.cmegroup.com/trading/energy/crude-oil/light-sweet-crude_quotes_settlements_options.html?tradeDate=03/07/2018"
This still returns the table for the current date. ie. 03/08/2018
However it does work if I add another query for contract month as well:
eg. http://www.cmegroup.com/trading/energy/crude-oil/light-sweet-crude_quotes_settlements_options.html?optionExpiration=190-M18&tradeDate=03/07/2018"
But if I then query:
eg. http://www.cmegroup.com/trading/energy/crude-oil/light-sweet-crude_quotes_settlements_options.html?optionExpiration=190-M18&tradeDate=03/06/2018"
....it will not give me the table for 03/06/2018.
It only appears to update the html for me when I change two or more query parameters in the Url. I'm fairly much a Noob with Html so I'm not sure if it's something to do with the actual website 'blocking' my request. Or does it expect some 'user interaction'?
The very 'basic' core of my code is:
using HtmlAgilityPack;
HtmlDocument htmlDoc = new HtmlDocument { OptionFixNestedTags = true };
HtmlWeb web = new HtmlWeb();
htmlDoc = web.Load(url);
A step in the right direction would be great.
Thank you.
Its a ajax site. the WepPage contains JS that makes Ajax queries by filtering done.
Therefore you do not need the html-agility-pack but the JSON.NET.
the URL is:
http://www.cmegroup.com/CmeWS/mvc/Settlements/Options/Settlements//190/OOF?monthYear=LOM18&strategy=DEFAULT&tradeDate=03/08/2018&pageSize=50&_=1520581128693
you need build the url query string, download th text with WebClient.DownloadString and convert it to POCO with JSON.NET.
Okay, so I've posted this answer for anyone else reading this post. Please feel free to comment or edit the post. Thanks again to Dovid for the suggestions. I cannot vouch for absolute syntax validity, however it's pretty close. The code loads a webpage table in Json format then saves to a file. There is a method for loading from the Json file as well. The code is 'as-is' and is not meant to be a copy and paste job, just a reference to how I did it.
using Newtonsoft;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
private string _jsonStr;
private string _tableUrlStr = "http://www.cmegroup.com/CmeWS/mvc/Settlements/Options/Settlements//190/OOF?monthYear=LOM18&strategy=DEFAULT&tradeDate=03/08/2018&pageSize=50&_=1520581128693";
using (WebClient wc = new WebClient)
{
wc.BaseAddress = #"http://www.cmegroup.com/";
wc.Headers[HttpRequestHeader.ContentType] = "application/json";
wc.Headers[HttpRequestHeader.Accept] = "application/json";
_jsonStr = wc.DownloadString(_tableUrlStr);
}
if (_jsonStr.IsNullOrEmpty())
return;
JObject jo = JObject.Parse(_jsonStr);
//## Add some more detail to the Json file.
jo.Add("instrumentName", "my instrument name");
jo.Add("contract", "my contract name");
//## For easier debugging but larger file size.
_jsonStr = jo.ToString(Formatting.Indented);
//## Json to file:
string path = directoryString + fileString + ".json";
if (!Directory.Exists(directoryString))
{
Directory.CreateDirectory(directoryString);
}
if (File.Exists(path))
{
return;
}
using (FileStream fileStream = new FileStream(path, FileMode.CreateNew, FileAccess.Write))
{
using (var streamWriter = new StreamWriter(fileStream, Encoding.UTF8))
{
streamWriter.WriteLine(_jsonStr);
streamWriter.Close();
}
}
//## Json file to collection:
//## Can copy and paste your Json at 'www.json2csharp.com'.
public class Settlement
{
public string strike { get; set; }
public string type { get; set; }
public string open { get; set; }
public string high { get; set; }
public string low { get; set; }
public string last { get; set; }
public string change { get; set; }
public string settle { get; set; }
public string volume { get; set; }
public string openInterest { get; set; }
}
public class RootObject
{
public List<Settlement> settlements { get; set; }
public string updateTime { get; set; }
public string dsHeader { get; set; }
public string reportType { get; set; }
public string tradeDate { get; set; }
public bool empty { get; set; }
//## New added entries
public string instrumentName { get; set; }
public string contract { get; set; }
}
private static IEnumerable<Settlement> JsonFileToList(string directoryString, string fileString)
{
if (directoryString == null)
{
return null;
}
string path = directoryString + fileString + ".json";
if (!Directory.Exists(directoryString))
{
Directory.CreateDirectory(directoryString);
}
if (!File.Exists(path))
{
return null;
}
RootObject ro = JsonConvert.DeserializeObject<RootObject>(File.ReadAllText(path));
var settlementList = ro.settlements;
foreach (var settlement in settlementList)
{
//## Do something with this data.
Console.Writeline(String.Format("Strike: {0}, Volume: {1}, Last: {2}", settlement.strike, settlement.volume, settlement.last));
}
return settlementList;
}

Upload new images in Edit POST method

I have view where we can see existing image and I want to upload new images to existing , so images uploads to folders and info about image storing in database also I use view model
My View Models Class
public class FurnitureVM
{
...
public IEnumerable<HttpPostedFileBase> SecondaryFiles { get; set; }
public List<ImageVM> SecondaryImages { get; set; }
}
public class ImageVM
{
public int? Id { get; set; }
public string Path { get; set; }
public string DisplayName { get; set; }
public bool IsMainImage { get; set; }
}
My Edit Method
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(FurnitureVM model)
{
// Save the files
foreach (HttpPostedFileBase file in model.SecondaryFiles)
{
FurnitureImages images = new FurnitureImages();
string displayName = file.FileName;
string extension = Path.GetExtension(displayName);
string fileName = string.Format("{0}{1}", Guid.NewGuid(), extension);
var path = "~/Upload/" + fileName;
file.SaveAs(Server.MapPath(path));
model.SecondaryImages = new List<ImageVM> { new ImageVM { DisplayName = displayName , Path = path } };
}
// Update secondary images
IEnumerable<ImageVM> newImages = model.SecondaryImages.Where(x => x.Id == null);
foreach (ImageVM image in newImages)
{
FurnitureImages images = new FurnitureImages
{
DisplayName = image.DisplayName,
Path = image.Path ,
IsMainImage = false
};
furniture.Images.Add(images);
}
ViewBag.CategoryId = new SelectList(db.Categories, "CategoryId", "Name", furniture.CategoryId);
}
So image's info writes to db good , but when I go to Edit View second time I get an exception "object reference not set to an instance of an object" in line string displayName = file.FileName; I understand that I must create an instance of FurnitureImages , right? Okay then how I have to write code , something like this? images.DisplayName = file.Filename etc?? And Is my foreach loop right where i update? Thank you

How to avoid optimistic concurrency error for newly added record in EF

I use Entity Framework 6 in an MVC 5 project an there is an entity relations between Experiment and FileAttachment (one Experiment can gave many FileAttachment). During Edit an Experiment record, I load a ViewModel containing both of the entities and list the attachments under the experiment in edit mode:
The scenario is as explained below:
1) There is a Delete button for each FileAttachment and when user delete an attachment, it is deleted via AJAX and display an information message on the same modal window.
2) But, if the user add a new attachment after deleting an attachment and save the Experiment the following error is encountered:
"Store update, insert, or delete statement affected an unexpected
number of rows (0). Entities may have been modified or deleted since
entities were loaded."
I think the reason is that:
I load Experiment and FileAttachment entities and then I change the FileAttachment entity by deleting a record. But there is still the same entity loaded first time and I want to save the outdated entity. So, maybe I need to create an entity and fill it before saving, but context.Entry(f).State = EntityState.Added,
context.Entry(f).State = EntityState.Modified and context.Entry(f).State = EntityState.Unchanged does not make any sense. What should I do to simply attach the newly added FileAttachment to the database?
Models:
public class Experiment
{
[Key]
public int Id { get; set; }
public int Number { get; set; }
public string Name { get; set; }
//Navigation Properties
public virtual ICollection<FileAttachment> FileAttachments { get; set; }
}
public class FileAttachment
{
[Key]
public int Id { get; set; }
public int ExperimentId { get; set; }
public string FileName { get; set; }
public byte[] FileData { get; set; }
[HiddenInput(DisplayValue = false)]
public string FileMimeType { get; set; }
//Navigation Properties
public virtual Experiment Experiment { get; set; }
}
ViewModel:
public class ExperimentViewModel
{
//code omitted for brevity
[DataType(DataType.Upload)]
public IEnumerable<HttpPostedFileBase> FileUpload { get; set; }
public virtual ICollection<FileAttachment> FileAttachments { get; set; }
}
Controller:
public JsonResult Update([Bind(Exclude = null)] ExperimentViewModel model)
{
List<FileAttachment> fa = new List<FileAttachment>();
//code omitted for brevity (At this step I add all the attachments in the model to "fa" paarmeter)
//Mapping ViewModel to Entity Model (ExperimentViewModel > Experiment) :::::::::::::::::
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ExperimentViewModel, Experiment>();
});
IMapper mapper = config.CreateMapper();
//var source = new ExperimentViewModel();
var dest = mapper.Map<ExperimentViewModel, Experiment>(model);
////::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
repository.SaveExperimentWithAttachment(dest, fa);
}
Concrete:
public void SaveExperimentWithAttachment(Experiment experiment, IEnumerable<FileAttachment> fileAttachment)
{
//Update Block
using (var context = new EFDbContext())
{
context.Entry(experiment).State = EntityState.Modified;
foreach (FileAttachment f in fileAttachment)
{
context.Entry(f).State = EntityState.Modified;
}
context.SaveChanges();
}
}
Here is the final answer by fixing the problems with the help of #StephenMuecke and #haim770. Many thanks both of you for your effort and kind help... Regards.
public void SaveExperimentWithAttachment(Experiment experiment, IEnumerable<FileAttachment> fileAttachment)
{
using (var context = new EFDbContext())
{
context.Entry(experiment).State = EntityState.Modified;
foreach (FileAttachment f in fileAttachment)
{
// !!! I forgot to update the ExperimentId field for each file attachment
f.ExperimentId = experiment.Id;
context.Entry(f).State = EntityState.Added; //Because file attachment(s) is not updated, newly ADDED
}
context.SaveChanges();
}
}

How to join my tables with identity tables?

I started a default MVC project with Identity and EF.
In my app users will be able to create and edit some records.
In the table for these records, I want to have the ids of users who created the record and who updated lastly.
My model class is like:
public class Record
{
public int ID { get; set; }
public DateTime CreateTime { get; set; }
public string CreatingUserID { get; set; }
public string UpdatingUserID { get; set; }
public DateTime UpdateTime { get; set; }
public Enums.RecordStatus Status { get; set; }
}
And in RecordsController, I save new records to db like this:
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(FormCollection form, RecordCreateVM vm)
{
string userId = User.Identity.GetUserId();
DateTime now = DateTime.Now;
Record rec = new Record ();
if (ModelState.IsValid)
{
int newRecordId;
using (RecordRepository wr = new RecordRepository())
{
UpdateModel(rec);
rec.CreateTime = now;
rec.UpdateTime = now;
rec.CreatingUserID = userId;
rec.UpdatingUserID = userId;
rec.Status = Enums.RecordStatus.Active;
Record result = wr.Add(rec);
wr.SaveChanges();
newRecordId = result.ID;
}
}
}
When I am listing these records, I also want my page to display these users' usernames.
I get all the active records from the repository I created.
public ActionResult Index()
{
RecordListVMviewModel = new RecordListVM();
using (RecordRepository wr = new (RecordRepository())
{
viewModel.Records = wr.GetAll();
}
return View(viewModel);
}
And this is the repository code:
public class RecordRepository: Repository<Record>
{
public override List<Record> GetAll()
{
IQueryable<Record> activeRecords = DbSet.Where(w => w.Status == Enums.RecordStatus.Active);
return activeRecords.ToList();
}
}
Where do I have to make changes? Can you give me an sample code for usages like this?
Thank you.
You need to change
public string CreatingUserID { get; set; }
public string UpdatingUserID { get; set; }
to something like:
public User CreatingUser { get; set; }
public User UpdatingUser { get; set; }
Set the ID's during the creation of new RecordRepository()
Then access them as Record.CreatingUser.FirstName ect

Resources