MVC5 Razor Upload Image - c#

I'm trying to create a page that whilst editing an "Asset" the user can upload a picture within a partial view.
On submitting the picture I would like the filename to be saved to server location and prefixed with its Asset ID number for obvious reasons and then return the partial view but with the picture in.
So when the user submits the edit page the changed details as well as a new shiny picture url is saved to the DB.
Heres what I have so far.
Edit View (Edit.cshtml)
#model Asset_Manager.DB.Asset
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Asset</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.Aid)
**** other fields
<div class="form-group">
#Html.LabelFor(model => model.Picture_Location, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.Partial("~/Views/Asset/UploadAssetImage.cshtml",Model)
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Partial Upload View (UploadAssetImage.cshtml)
#model Asset_Manager.DB.Asset
#using (Html.BeginForm("UploadPicture", "Asset", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<img src="#Model.Picture_Location" alt="#Model.Description" width="250" height="250" /><br />
<input type="file" name="file" />
<input type="submit" name="Submit" id="Submit" value="Upload" />
<input type="hidden" name="id" value="#Model.Aid" />
}
and finally Controller Method (AssetController.cs)
[HttpPost]
public ActionResult UploadPicture(int id,FormCollection collection)
{
if (Request.Files.Count > 0)
{
var file = Request.Files[0];
if (file != null && file.ContentLength > 0)
{
var fileName = "Asset_" + id + "_" + Path.GetFileName(file.FileName);
var path = Path.Combine(Server.MapPath("~/Content/AssetImages/"), fileName);
file.SaveAs(path);
}
}
Asset A = new Asset();
A = _dal.GetAssetByID(id);
return PartialView("UploadAssetImage", A.Aid);
}
Now My Issues
Each time I try to submit a photo I get kicked right out to the Asset index (Index.cshtml) page let alone be able to see if sending the entire edit works.
Also the breakpoint under the controller method doesn't trigger so I cant trace where the issue could be.
Any Help / Examples / Pointers in the right direction would be appreciated.

You have a form within a form, which is invalid HTML. The outermost form is what is being submitted, and this form, importantly, does not include the enctype="multipart/form-data" attribute. Add that attribute to your form in the view and remove the form in your partial.

Related

File upload ASP.NET MVC in multiple submits form

I have a small tool that downloads reports based on the specified options. The download works well. And now, I want to also upload a file to the folder and then further use it.
The problem is that I already have one submit button on the form that is used for the download and when I am adding another button for the upload, only download is triggered.
I tried to resolve it using an #Html.ActionLink(), but no success. Is there any proper way to resolve the issue? I know that there is a possibility to capture the submit value and then check in one main ActionResult in the Controller and redirect to the respective ActionResult, but I don't want to do it, since there are too many POST Actions in one controller.
Here is my View - download.cshtml:
#using (Html.BeginForm())
{
<fieldset>
<div class="title">Click to download report</div>
<div class="field">
<input id="downloadBtn" type="submit" class="button" value="Download" />
</div>
</fieldset>
<fieldset id="Option_ClientInfo">
<div class="title">
Image
</div>
<div class="field">
<input type="file" name="ImageUpload" accept="image/jpeg" />
<p>#Html.ActionLink("Upload", "UploadImage", new { controller = "Home", enctype = "multipart/form-data"}, new { #class = "button" })</p>
</div>
</fieldset>
}
And the controller - HomeController.cs:
public partial class HomeController : Controller
{
// some functions
// ....
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult UploadImage(HttpPostedFileBase imageFile)
{
string path = Path.Combine(this.GetImageFolder, Path.GetFileName(imageFile.FileName));
imageFile.SaveAs(path);
return null;
}
// additional POST functions for other forms
// ....
[HttpPost]
public ActionResult Download(Info downloadInfo)
{
// perform checks and calculations
return new reportDownloadPDF(downloadInfo);
}
}
Any suggestion in appreciated.
The solution is just separate upload and download functionalities using two forms so it wont conflict while submitting.
#using (Html.BeginForm())
{
<fieldset>
<div class="title">Click to download report</div>
<div class="field">
<input id="downloadBtn" type="submit" class="button" value="Download" />
</div>
</fieldset>
<fieldset id="Option_ClientInfo">
<div class="title">
Image
</div>
</fieldset>
}
#using (Html.BeginForm("UploadImage", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<fieldset>
<div class="field">
<input type="file" name="ImageUpload" accept="image/jpeg" />
<p>
<input id="uploadBtn" type="submit" class="button" value="Upload" />
</p>
</div>
</fieldset>
}
There is another issue as well. Image control name and Post Action method parameter name should be same.
So your upload image Post Action method will be:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult UploadImage(HttpPostedFileBase imageUpload)
{
string path = Path.Combine(this.GetBasePath + "/img/tmp/", Path.GetFileName(imageFile.FileName));
imageFile.SaveAs(path);
return null;
}

#Html.DropDownList return null to controller

The DropDownList in my view shows the relevant options to choose, but no matter what i choose, the folders in the Controller get value null.
Why? How can i fix it so the folders in the Controller will get the chosen option from the DropDownList from the view?
P.S - I have no Model.
This is my Controller:
//POST: Home
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(IEnumerable<HttpPostedFileBase> file, string folder, IEnumerable<SelectListItem> folders)
{
// some code here
}
This is my view:
#using (Html.BeginForm("Index", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken();
<div class="container">
<div class="form-horizontal">
<div class="form-group">
<p></p>
<label for="file">Upload Photo:</label>
<input type="file" name="file" id="file" accept="image/*" multiple="multiple"/>
</div>
<div class="form-group">
<div>
<label>Choose Album:</label>
#if (ViewBag.Folders != null)
{
#Html.DropDownList("folders", new SelectList(ViewBag.Folders as IEnumerable<SelectListItem>, "Value", "Text"), "--- Select Album ---", new { #class = "form-control" })
}
</div>
</div>
<div class="form-group">
<div>
<input type="submit" value="Upload" class="btn btn-default" />
</div>
</div>
</div>
</div>
}
Thanks.
in order to get the folder, give your dropdownlist the corresponding name ...
#Html.DropDownList("folders"...
will result in your DDL to have the name "folders" ... which will try to post back a single item... folders in your method is a IEnumerable<SelectListItem> ... the modelbinder is incapable to convert that ...
try
#Html.DropDownList("folder"...
note the missing s
now the name corresponds to the string folder parameter in your method... which the binder will most likely be able to bind for you...
if you debug errors like this, use the debugger to have a look at HttpContext.Request.Params, which will show you what was coming back when the request was made...
Parameter type should be changed from IEnumerable to String as view returns only selected item NOT collection.
//POST: Home
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(IEnumerable<HttpPostedFileBase> file, string folder, string folders)
{
// some code here
}
Just wanted to give another option. If you're able to leverage a client-side solution, then you could muscle this a bit with a hidden input value and JQuery.
Add a hidden input control:
<input name="selectedFolder" type="hidden" value="" />
Add some Jquery:
<script type="text/javascript">
$(function(){
$("#folders").on("change", function {
$("#selectedFolder").val($(this).text());
});
});
</script>
Thank you Sakthivel Ganesan, after changing the IEnumerable<SelectListItem> folders in the Controller to string folders, the only thing left to do is to change my Html.DropDownList Value to Text like this...
from :
#Html.DropDownList("folders", new SelectList(ViewBag.Folders, "Value", "Text"), "--- Select Album ---", new { #class = "form-control" })
To:
#Html.DropDownList("folders", new SelectList(ViewBag.Folders, "Text", "Text"), "--- Select Album ---", new { #class = "form-control" })

Post data from a partial view to another action

Just started with MVC and trying to do the following:
having a Details.cshtml with the following part on one of my tabs:
<!-- DETAILS TAB CONTENT -->
<div class="tab-pane profile active" id="details-tab">
#if (ViewBag.ScreenMode == Constants.ScreenMode.View)
{
#Html.Partial("_ViewDetails", Model)
}
else
{
#*#Html.Partial("_EditDetails", Model)*#
<div id="DetailsEdit">
#{Html.RenderPartial("_EditDetails");}
</div>
}
</div>
My _ViewDetails.cshtml shows detail information and has the following to go to the Edit mode:
#Html.ActionLink("Edit", "Details", new { id = Model.EmployeeId, screenMode = Constants.ScreenMode.Edit })
When clicked, indeed the tab shows the contents of the partial view _EditDetails. My _EditDetails.cshtml looks like this:
#using (Html.BeginForm("Edit", "Employee", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.EmployeeId)
#* fields with editable controls. Left it out here *#
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
and having the following methods in my controller:
public ActionResult Details(Guid? id, Constants.ScreenMode? screenMode)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Employee employee = employeeManager.Get(id.GetValueOrDefault());
if (employee == null || employee.EmployeeId == null || employee.EmployeeId == Guid.Empty)
return HttpNotFound();
if (screenMode == null)
screenMode = Constants.ScreenMode.View;
ViewBag.ScreenMode = screenMode;
return View(employee);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Guid? id, HttpPostedFileBase upload)
{
// code here
return RedirectToAction("Details", "Employee", new { id = employeeToUpdate.EmployeeId, screenMode = Constants.ScreenMode.View });
}
However, when I press the save button from the edit partial view, it never hits my code Edit action in my controller. It only goes back to the Details action, which gets the info again. I thought by using the following at the beginning of my partial view, I could make the post go to my Edit action:
#using (Html.BeginForm("Edit", "Employee", FormMethod.Post, new { enctype = "multipart/form-data" }))
What do I miss or are my thoughts completely wrong?
Thanks in advance.
Aarghhhh, found it.
My Details.cshtml started with:
#using (Html.BeginForm("Details", "Employee", null, FormMethod.Post, new { enctype = "multipart/form-data" }))
Removed this and now it's working.
Overlooked this for days.
Thanks.

MVC Ajax.BeginForm and Content Security Policy

To prevent cross side scripting i implement CSP to one of my applications. At moment i´m reconfigure all html classes, so that javascript always comes from my server.
Now i found a page with an Ajax.BeginForm and always get the error "Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self'"." if i want to submit the form and update the view.
Can anybody help me, where the problem is?
Here is my html classes (shorted):
UserInformation.cshtml:
<div id="OpenAccountInformation">#Html.Action("OpenAccountInformation")</div>
</div>
AccountInformation.cshtml:
#Scripts.Render("~/Scripts/bundles/ManageUsers/AccountInformation")
#model Tumormodelle.Models.ViewModels.AzaraUserModel
<input type="hidden" value="#ViewBag.Editable" id="EditableUserInformation">
<div id="Editable">
#using (Ajax.BeginForm("EditUser", "ManageUsers", new AjaxOptions { InsertionMode = InsertionMode.Replace, UpdateTargetId = "OpenAccountInformation", HttpMethod = "post", }))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(m => m.UserID)
<div>
<div>
#Html.LabelFor(m => m.Username, new { #class = "entryFieldLabel" })
</div>
</div>
<div>
<div>
<button name="button" value="save" class="formbutton" id="saveButton">save</button>
<button name="button" value="cancel" class="formbutton" id="cancelButton">cancel</button>
</div>
}
</div>
<div id="NonEditable">
<div>
<div>
#Html.LabelFor(m => m.Username, new { #class = "entryFieldLabel" })
</div>
</div>
<div>
<div>
<button name="button" value="edit" class="formbutton" id="editButton" type="button">edit</button>
</div>
</div>
</div>
and the c# methods:
public ActionResult EditUser(AzaraUserModel AzaraUserModel, string button)
{
if (button == Tumormodelle.Properties.Resources.Save)
{
if (ModelState.IsValid)
{
azaraUserManagement.Update(AzaraUserModel.Username, AzaraUserModel.Title, AzaraUserModel.FirstName, AzaraUserModel.LastName, AzaraUserModel.EMailAddress, null, AzaraUserModel.Phone, AzaraUserModel.UserID, (byte)AzaraUserModel.ShowMail.ID);
ViewBag.Message = Tumormodelle.Properties.Resources.Personal_Data_Changed;
ViewBag.Editable = true;
}
else ViewBag.Editable = false;
BindShowMailList();
return PartialView("AccountInformation", AzaraUserModel);
}
else
{
return RedirectToAction("OpenAccountInformation", "ManageUsers");
}
}
public ActionResult UserInformation()
{
return View("UserInformation");
}
public PartialViewResult OpenAccountInformation()
{
AzaraUserModel AzaraUserModel = new AzaraUserModel(azaraUserManagement.GetSingle(AzaraSession.Current.UserComparison.GetUser().Id));
BindShowMailList();
ViewBag.Editable = true;
return PartialView("AccountInformation", AzaraUserModel);
}
Edit: With help of Chrome debugger i find out, that the error is thrown in the moment form becomes submited.
Ajax.BeginForm will be generating inline script in the generated HTML of your page, which you have disallowed by use of script-src 'self' in your Content Security Policy.
If you want to use the CSP to prevent any inline injected scripts you must use Html.BeginForm instead and add the JavaScript to submit this via Ajax in an external .js file.
try to add this attribute to your controller post action
[ValidateInput(false)]

Rendering partial view inside a view using AJAX

I'm trying to refresh a partial view inside of a view when a form is submitted. However, whenever I try it just renders the partial view as a normal view. Can someone tell me what I'm doing wrong?
Controller:
public ActionResult ChangeHeatName(string heatName, string updatedHeat)
{
string user = User.Identity.Name;
HomeModel H = new HomeModel();
H.ChangeHeatName(heatName, updatedHeat, user);
ChemViewModel mySlagViewModel = new ChemViewModel();
mySlagViewModel = H.QueryResults(heatName);
return PartialView("PartialChemAnalysis", mySlagViewModel);
}
Partial view form (contained in partial view, not main view):
#using (Ajax.BeginForm("ChangeHeatName", "Home", new AjaxOptions(){UpdateTargetId = "chemDiv" InsertionMode = InsertionMode.Replace}))
{
<section>
Heat Name:<input type="text" name="heatName" value="#Html.ValueFor(x => x.heatname)" style ="width:100px"/>
Change to:<input type="text" name="updatedHeat" value="" style="width: 100px" />
<input type="submit" name="ChangeHeatName" value="Change" />
</section>
}
Index view where partial view is being rendered:
#if(ViewBag.SearchKey == null)
{
<div class="content-wrapper">
<hgroup class="title">
<h1>#HttpContext.Current.User.Identity.Name</h1>
<h2>#ViewBag.Message</h2>
</hgroup>
</div>
}
#using (Html.BeginForm("Index", "Home", "POST"))
{
<div class="searchField">
<input type="text" class="search-query" name="heatSearch" placeholder="Search">
<button class="btn btn-success" type="submit">Search</button>
<br />
#if (ViewBag.AverageSuccessful == true)
{
<input type="text" name="AvgConfirmation" class="search-query" value="Average Submitted Successfully" width:"400px" placeholder="Search" />
}
</div>
}
#if(ViewBag.SearchKey != null)
{
<div>
<div id ="chemDiv">
#Html.Action("PartialChemAnalysis", "Home", (string)ViewBag.SearchKey)
</div>
<div id ="slafDiv">
#Html.Action("PartialSlagView", "Home", (string)ViewBag.SearchKey)
</div>
</div>
}
Index controller that passes SearchKey:
[HttpPost]
public ActionResult Index(string heatSearch)
{
ViewBag.SearchKey = heatSearch;
return View();
}
Currently your ajax.beginform is in your partial view, that's all fine and dandy, but your partialview is not rendered inside your index, so really your never doing the ajax replace logic you're just calling a the action method and getting a full page refresh of the partial view.
here's what would work.
#if(ViewBag.SearchKey != null)
{
<div>
<div id ="chemDiv">
#Html.Partial("ChangeHeatName")
</div>
<div id ="slafDiv">
#Html.Action("PartialSlagView", "Home", (string)ViewBag.SearchKey)
</div>
</div>
}
Now your Ajax.Beginform is rendered in the index view, and when the button is clicked it will refresh.
Edit: you'll need to do something with #Html.Action("PartialChemAnalysis", "Home", (string)ViewBag.SearchKey) possibly stick it in your partial view, because everything in "chemDiv" will now be replaced upon update.
You're not specifying POST in Ajax.BeginForm(). Try this:
#using (Ajax.BeginForm("ChangeHeatName", "Home", FormMethod.Post,
new AjaxOptions(){UpdateTargetId = "chemDiv" InsertionMode = InsertionMode.Replace}))
{...}
Also, stick a breakpoint on your controller action and step through it, see if it's actually hitting return PartialView() or skipping over it.
Posting this because it's not an intuitive fix. Apparently there are issues with MVC 4 and jQuery 1.9.1 so to get this to work I had to change my reference to jQuery 1.7.1

Categories