I'm trying to figure out how to store multiplie files using a partial view. A screenshot of the partial view can be seen here.
The partial view itself works perfectly as long as I use regular form inputs. Storing a file however is a bit more complex. Since I'm using Entity First, I'm just storing a reference to the file instead of the actual file itself. This works fine for a single file. The byte reference gets stored to my database and I can generate a link to the file in a view which displays all the properties of a certain record. However, I'm clueless on how I should tackle this if I want multiple uploads. One for each added row.
I'm also not sure how I should target a seperate field of a certain row through jQuery as all rows get generated dynamically, but that's a different issue.
POST action of my controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreateSrv([Bind(Include = "ID,CreationTimestamp,EditTimestamp,requestStatus,Requester,Reponsible,DeliveryDate,Project,CostCenter, MailCounter, ExtendedOrders")] Request request, HttpPostedFileBase upload)
{
if (ModelState.IsValid)
{
if (upload != null && upload.ContentLength > 0)
{
if (upload.ContentLength > 0)
{
var fileName = Path.GetFileName(upload.FileName);
var path = Path.Combine(Server.MapPath("~/App_Data/uploads"), fileName);
upload.SaveAs(path);
using (var reader = new System.IO.BinaryReader(upload.InputStream))
{
request.ExtendedOrders.ToArray()[0].File = reader.ReadBytes(upload.ContentLength);
}
}
}
request.CreationTimestamp = DateTime.Now;
request.EditTimestamp = DateTime.Now;
request.RequestStatus = "Initial";
request.RequestType = "Services";
db.requests.Add(request);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(request);
}
Controller method which adds rows to my partial view using AJAX:
[HttpGet]
public ActionResult AddOrderRowExtended()
{
OrderExtended order = new OrderExtended();
order.Units = GetSelectListItems(GetUnitsFromDB());
order.Unit = "PC";
order.Currencies = GetSelectListItems(GetCurrenciesFromDB());
order.Currency = "European Euro";
return PartialView("~/Views/Orders/OrderExtendedCreate.cshtml", order);
}
I then call the partial view in my "main" view. The addOrder buttons adds more rows dynamically:
@using (Html.BeginForm("CreateSrv", "Requests", null, FormMethod.Post, new { enctype = "multipart/form-data" }))
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
... some fields of my Request model...
<tbody id="OrderZone">
@foreach (var row in Model.ExtendedOrders)
{
Html.RenderPartial("~/Views/Orders/OrderExtendedCreate.cshtml", row);
}
</tbody>
<button class="btn btn-sm btn-success" type="button" id="addOrder">Add row</button>
<br />
<div class="body-content">
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
</div>
My OrderExtended model:
public class OrderExtended
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
....
[NotMapped]
[DataType(DataType.Upload)]
public HttpPostedFileBase Quotation { get; set; }
public byte[] File { get; set; }
//FK
public int RequestRefId { get; set; }
[ForeignKey("RequestRefId")]
public virtual Request Request { get; set; }
}
OrderExtendedCreate.cshtml
@model RFP_MVC.Models.OrderExtended
<tr class="editorRow">
@using (Html.BeginCollectionItem("ExtendedOrders"))
{
<td class="col-md-2">@Html.EditorFor(model => model.Material, new { htmlAttributes = new { @class = "input-sm form-control" } })</td>
<td class="col-md-1">@Html.EditorFor(model => model.Quantity, new { htmlAttributes = new { @class = "input-sm form-control" } })</td>
<td class="col-md-2">@Html.DropDownListFor(model => model.Unit, Model.Units, new { id = "Unit", @class = "input-sm form-control select2" })</td>
<td class="col-md-2">@Html.DropDownListFor(model => model.PurchGroup, Model.PurchGroupList, "Select...", new { @class = "input-sm form-control" })</td>
<td class="col-md-2">
<div class="input-group">
<span class="input-group-btn">
<button class="btn btn-sm btn-default" type="button" data-toggle="modal" data-target="#myModal"><span class="glyphicon glyphicon-search"></span></button>
</span>
@Html.EditorFor(model => model.Vendor, new { htmlAttributes = new { id = "Vendor" , @class = "input-sm form-control" } })
</div>
</td>
<td class="col-md-1">@Html.EditorFor(model => model.OrderingCode, new { htmlAttributes = new { @class = "input-sm form-control" } })</td>
<td class="col-md-1">@Html.EditorFor(model => model.Price, new { htmlAttributes = new { @class = "input-sm form-control" } })</td>
<td class="col-md-1">@Html.DropDownListFor(model => model.Currency, Model.Currencies, new { id = "Currency", @class = "input-sm form-control select2" })</td>
<td class="col-md-2">
<span class="btn btn-sm btn-default btn-file glyphicon glyphicon-folder-open"><input type="file" name="upload"></span>
</td>
}
</tr>
Thanks in advance
Changed my POST method
Changed the parameter to an IEnumerable (as suggested by ramiramilu):
IEnumerable files
Loop through all the files and process them:
int fileIndex = 0;
foreach (HttpPostedFileBase upload in files)
{
if (upload != null && upload.ContentLength > 0)
{
var fileName = Path.GetFileName(upload.FileName);
var path = Path.Combine(Server.MapPath("~/uploads"), fileName);
upload.SaveAs(path);
using (var reader = new System.IO.BinaryReader(upload.InputStream))
{
request.ExtendedOrders.ToArray()[fileIndex].File = reader.ReadBytes(upload.ContentLength);
}
fileIndex++;
}
}
If you want to upload multiple files, the you need to expect IEnumerable<HttpPostedFileBase> upload as a parameter to your Action. Once you get IEnumerable<HttpPostedFileBase> upload, use foreach and save each file.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With