I have an MVC3 form bound to a model with a file upload control. (Extra HTML removed for brevity):
#model Models.MessageModel
<script type="text/javascript">
var numAttachments = 0;
$(function () {
$(".add-attachment").click(function () {
$(".attachments").append("<div><input type=\"file\" name=\"attachments\" id=\"attachment" + numAttachments + "\" /></div>");
});
});
</script>
#using (Html.BeginForm())
{
#Html.ValidationSummary()
<div class="field-label">Subject:
#Html.EditorFor(model => model.Subject)
</div>
<div class="attachments">
</div>
<div>
Add Attachment
</div>
<div class="message-text">#Html.TextAreaFor(model => model.Text, new { cols = 107, rows = 10 })</div>
<input type="submit" value="Send Message" />
</div>
}
Users can choose to add multiple attachments by clicking the "add attachment" link, attachments are not required.
My model is as follows:
public class MessageModel
{
[Required]
public string Subject { get; set; }
[Required]
public string Text { get; set; }
public IEnumerable<HttpPostedFileBase> Attachments { get; set; }
}
(NOTE: I've also tried moving the attachments out of the model, into an argument to my action method with the same results)
My Action:
[HttpPost]
public ActionResult New(MessageModel message)
{
// this check passes if no file is uploaded
// but once a file is uploaded, this evaluates to false
// even if the model is valid
if (ModelState.IsValid)
{
// do stuff
}
}
This form works fine and validation passes when no file is selected for upload. When I choose a file for upload, ModelState.IsValid becomes false. How can I cause validation to ignore uploaded files?
You need to make sure your form is using the correct "enctype".
#using (Html.BeginForm("New", "Controller", FormMethod.Post, new { enctype = "multipart/form-data" }))
MVC 3 file upload and model binding
Related
I am creating an application where a user can post, something like a forum. I am trying to load an image and text into the database, for that, I have two tables and they are both related through an id. I figured out how to load text content, but I really don't know how to do the same with image content.
I'm using an MVC view with it respective controller to consume the API.
The model I'm using:
public class ForumThemeModel
{
public ForumThemeModel()
{
Themes = new HashSet<Theme>();
}
//forum table
[Key]
public int IdForum { get; set; }
public string PostTittle { get; set; }
//theme table
public int? Idtheme { get; set; }
public string TextContent { get; set; }
public int? IdForum { get; set; }
public IFormFile ContentFile { get; set; } //where the image will be stored
public string FileNameA { get; set; }
public string FileType { get; set; }
}
The API controller where the post creation is made:
[HttpPost]
[Consumes("multipart/form-data")]
[Route("createPost")]
public async Task<IActionResult> createPost([FromBody]ForumThemeModel model)
{
Forum forum = new Forum();
Theme theme = new Theme();
var fileName = Path.GetFileName(model.FileNameA);
var fileExt = Path.GetExtension(fileName);
var newFileName = String.Concat(Convert.ToString(Guid.NewGuid()), fileExt);
using(var target = new MemoryStream())
{
await model.ContentFile.CopyToAsync(target);
theme.ContentFile = target.ToArray();
}
forum.PostTittle = model.PostTittle;
theme.FileNameA = newFileName;
theme.FileType = fileExt;
var postTittle = new SqlParameter("#postTittle", forum.PostTittle);
var textContent = new SqlParameter("#textContent", theme.TextContent);
var content_file = new SqlParameter("#content_file", theme.ContentFile);
var file_name_a = new SqlParameter("#file_name_a", theme.FileNameA);
var FileType = new SqlParameter("#FileType", theme.FileType);
//saves everything to database
return Ok();
}
The MVC controller where the API is used:
[HttpPost]
public IActionResult createPost(ForumThemeModel model)
{
HttpClient hc = new HttpClient();
hc.BaseAddress = new Uri("https://localhost:44325/api/Users");
var userPost = hc.PostAsJsonAsync<ForumThemeModel>("Users/createPost", model);
userPost.Wait();
//do something when the resquest result is successful
}
The view of the MVC controller:
#model Huerbog.Models.Request.ForumThemeModel
#{
ViewData["Title"] = "CreatePost";
}
<hr />
<div class="row">
<div class="col-12 col-md-9">
<form enctype="multipart/form-data" asp-action="createPost">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group h4">
<label asp-for="PostTittle" class="control-label">Tittle</label>
<input asp-for="PostTittle" class="form-control" />
<span asp-validation-for="PostTittle" class="text-danger"></span>
</div>
<div class="form-group h4">
<label asp-for="Content" class="control-label">Content of the post</label>
<textarea rows="5" asp-for="Content" class="form-control"></textarea>
<span asp-validation-for="Content" class="text-danger"></span>
</div>
<div class="form-group h4">
<label asp-for="ContentFile" class="control-label">Imagen:</label><br />
<input class="form-control-file" multiple asp-for="ContentFile" type="file" />
</div>
<div class="form-group h4">
<input type="submit" value="Create post" class="btn btn-primary" />
</div>
</form>
</div>
</div>
So I have read about uploading images or files and it indicates that the attribute enctype = "multipart / form-data" should be present in the view when uploading images or different types of files in a database, due to that attribute two things happen: the first is that when I leave the attribute in the view the MVC controller does not use the API controller, but the model receives the information from the file or image such as the name, the file type, etc. The second is when I remove the attribute from the view, in this case the MVC controller makes use of the API controller, but the model does not contain anything regarding the file or the image. In both cases the text content is received by the model, but it is also not saved by the database due to the lack of information regarding the image.
I'm not very sure if the enctype = "multipart / form-data" atribute is the main reason, but as I said before, the text content, before the image upload, was working well and the enctype = "multipart / form-data" was not present.
I've stuck on the img upload for a while and really I don't know how to do it, any help is appreciated.
public IFormFile ContentFile { get; set; }
Firstly, please note that PostAsJsonAsync method would serialize the model data as JSON then send in the request body, which does not make sense to serialize a FormFile, it would cause an error.
To achieve the requirement of consuming an API from MVC controller action to upload file(s) with other data, you can refer to the following example code.
[HttpPost]
public async Task<IActionResult> CreatePost(ForumThemeModel model)
{
var formContent = new MultipartFormDataContent();
formContent.Add(new StringContent(model.PostTittle), "PostTittle");
//...
//for other properties, such as FileNameA, FileType etc
//...
formContent.Add(new StreamContent(model.ContentFile.OpenReadStream()), "ContentFile", Path.GetFileName(model.ContentFile.FileName));
HttpClient hc = new HttpClient();
hc.BaseAddress = new Uri("https://localhost:44325/api/Users/");
var userPost = await hc.PostAsync("createPost", formContent);
//...
API action
public async Task<IActionResult> createPost([FromForm]ForumThemeModel model)
{
//...
With the below code how can I get the value of the dropdown list using page handlers or tag helpers?
One way I've been reading is using OnChange on the dropdown, I can set the value of a hidden field with javascript then get value of hidden field from code behind. Should I even be using page handlers/tag helpers to get the dropdown value?
I have tried using Request.Form["networks"] but this just gives the ID and not the Value.
<form method="post">
#Html.DropDownList("networks",
Model.GetNetworks().Select(s => new SelectListItem()
{
Text = s.networks,
Value = s.id.ToString(),
}),
new
{
#class = "dropdown form-control",
})
<input type="hidden" id="selectedwifivalue" />
<br />
<input type="text" placeholder="enter wifi password" asp-for="Password" class="input-sm form-control" />
<br />
<button asp-page-handler="submit" class="btn btn-primary btn-sm form-control">Submit</button>
</form>
Model is
public class WifiNetworks
{
public int id { get; set; }
public string networks { get; set; }
public IEnumerable<SelectListItem> UserNetworks { get; set; }
}
Code behind in cshtml file
[BindProperty]
public string Password { get; set; }
public string networks { get; set; }
public void OnPostSubmit()
{
{
var password = Request.Form["password"];
var network = Request.Form["networks"]; <--this just gives the ID and not the value
var wifi = networks; <---this is blank
}
}
To access the form elements value, You can use Request.Form collection by passing the name of the form element.Set name property of html element and check.
Below is sample code for get form element value using name property.
View :
#using (Html.BeginForm("ReceiveValueWithRequestFormData", "ControllerAndAction"))
{
<ol>
<li>
#Html.Label("Username")
#Html.TextBox("txtUserName") : user
</li>
<li>
#Html.Label("Password")
#Html.TextBox("txtPassword") : pass
</li>
</ol>
<input type="submit" value="Login" />
}
<div id="divResult"></div>
Controller :
[HttpPost]
public ActionResult ReceiveValueWithRequestFormData()
{
string userName = Request.Form["txtUserName"];
string password = Request.Form["txtPassword"];
if (userName.Equals("user", StringComparison.CurrentCultureIgnoreCase)
&& password.Equals("pass", StringComparison.CurrentCultureIgnoreCase))
{
return Content("Login successful !");
}
else
{
return Content("Login failed !");
}
}
In the end I could have done it using ajax but using the hidden field approach it works fine like below no idea if this is a good/bad/better way to do this (I seemed to be asking those questions quite alot)
get value of dropdown using jquery "change" function (keep this outside of the
$(document).ready function as it wont fire)
$('#dnetworks').change(function () {
var selectedwifi = $('#networks option:selected').text();
$('#hiddenselectedwifivalue').val(selectedwifi); //set value of hidden field
});
Razor page
<input type="hidden" id="hiddenselectedwifivalue" asp-for="WifiNetworks.networks" />
.cshtml file
var selectedwififromdd = WifiNetworks.networks;
I used the bind for the whole class in the model page (.cshtml file)
public class WifiModel : PageModel
{
[BindProperty] public WifiNetworks WifiNetworks { get; set; }
I'm really a begginer at programing so ease stuff are hard for me if can anyone explain me or help me in my situation I would apreciate a lot.
On my create method I need to save a file (pdf in db).
What I have now:
Model:
public class Candidate : BaseEntity
{
public int Id { get; set; }
public string Name { get; set; }
public int Number { get; set; }
public string Profile { get; set; }
public Byte[] CV { get; set; }
}
Controller:
[HttpPost("UploadFiles")]
public IActionResult Post(List<IFormFile> files)
{
long size = files.Sum(f => f.Length);
// full path to file in temp location
var filePath = Path.GetTempFileName();
foreach (var formFile in files)
{
if (formFile.Length > 0)
{
using (var stream = new MemoryStream())
{
files.CopyTo(stream);
//await formFile.CopyToAsync(stream);
}
}
}
// process uploaded files
// Don't rely on or trust the FileName property without validation.
return Ok(new { count = files.Count, size, filePath });
}
In my view i'm using this to attach the file:
<form method="post" enctype="multipart/form-data" asp-controller="UploadFiles" asp-action="Index">
<div class="form-group">
<div class="col-md-10">
<p>Upload one or more files using this form:</p>
<input type="file" name="files" multiple />
</div>
</div>
<div class="form-group">
<div class="col-md-10">
<input type="submit" value="Upload" />
</div>
</div>
</form>
You're not going to get a file path to work with. An uploaded file would be in the HTTP request, but you haven't set your action method up to accept that properly. You really should read some tutorials about how to upload files in ASP.NET MVC so you have the general idea, rather than asking someone on Stack Overflow to explain what you need to do.
#mason.
This is not a complete answer to your problem but it will show you how file uploading works in asp.net mvc. You will have to modify it according to your problem.
Here’s a form that will post back to the current action.
#using (Html.BeginForm("Upload", "Upload", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<input type="file" name="file" />
<input type="submit" name="Submit" id="Submit" value="Upload" />
}
Here’s the action method that this view will post to which saves the file into a directory in the App_Data folder named “uploads”.
[HttpPost]
public ActionResult Upload(HttpPostedFileBase file) {
if (file.ContentLength > 0)
{
var fileName = Path.GetFileName(file.FileName);
var path = Path.Combine(Server.MapPath("~/App_Data/uploads"), fileName);
file.SaveAs(path);
}
return RedirectToAction("Index");
}
Visit this for complete solution
Again this answer is only to understand how we made HTTP request to upload files and how to save files.
Below is a very simple MVC triplet, implemented to try the things out.
It seems to work except the URL I get after changing a Status code to say 500 and hitting Submit button is Error/StatusCode, not Error/StatusCode/500. How can I change it?
I appreciate it is a simple question, but I could not find correct key words to google out the answer.
Model
public class ErrorModel
{
[DisplayName("Status Code")]
public string StatusCode { get; set; }
public ErrorModel(string statusCode)
{
HttpStatusCode code;
if (! Enum.TryParse(statusCode, out code))
{
code = HttpStatusCode.NotFound;
}
StatusCode = ((int)code).ToString();
}
public ErrorModel(HttpStatusCode statusCode = HttpStatusCode.OK)
{
StatusCode = ((int)statusCode).ToString();
}
}
View
#using WebApplication1.Models
#model WebApplication1.Models.ExcelModel
#using (Html.BeginForm("StatusCode", "Error", FormMethod.Post))
{
<p>
<div class="editor-label">
#Html.LabelFor(model => model.StatusCode)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.StatusCode, null, "id")
</div>
<input type="submit" value="Submit" />
</p>
}
Controller
public class ErrorController : Controller
{
static readonly List<HttpStatusCode> ErrorCodes = new List<HttpStatusCode>(){
HttpStatusCode.Unauthorized, HttpStatusCode.Forbidden, HttpStatusCode.NotFound, HttpStatusCode.InternalServerError};
public ActionResult StatusCode(string id)
{
ViewBag.Message = "";
if ((id == null) || (ErrorController.AreEqual(HttpStatusCode.OK, id)))
{
return View("StatusCode", new ErrorModel(HttpStatusCode.OK));
}
foreach (HttpStatusCode errorCode in ErrorCodes)
{
if (ErrorController.AreEqual(errorCode, id))
{
return View("HttpError", new ErrorModel(errorCode));
}
}
ViewBag.Message = "Exception " + id
+ #" is not supported, see https://msdn.microsoft.com/en-us/library/system.net.httpstatuscode(v=vs.110).aspx for further details";
return View("HttpError", new ErrorModel(HttpStatusCode.InternalServerError));
}
static private bool AreEqual(HttpStatusCode httpCode, string statusCode)
{
return (statusCode == ((int)httpCode).ToString());
}
}
You are using POST method to submit the form. That will not include the form element values in the url, but in the request body.
If you want your form field values to be part of the URL, Change the form method to GET. When using GET,if a form is posted, the data sent to the server is appended to the URL as query string values.
#using (Html.BeginForm("StatusCode", "Error", FormMethod.Get))
{
<div class="editor-label">
#Html.LabelFor(model => model.StatusCode)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.StatusCode, null, "id")
</div>
<input type="submit" value="Submit" />
}
Now when user submits the form, browser will issue a GET request to the Error/StatusCode url with the form data appended to the url.
/Home/StatusCode?id=500
Dynamically changing the action's url could be a solution and the code below shows a simplest way to do that.
<script>
$(document).ready(function() {
$('form').submit(function(){
this.action = this.action + '/' + $('#StatusCode').val();
})
});
</script>
I've got an MVC 3 form in a strongly typed view where one of the fields I need submitted is inside of a jQuery dialog. I have not been able to get this field to be part of the POST parameters submitted. Why oh why?
The View:
#model My.Models.DialogFieldModel
#{
ViewBag.Title = "Index";
}
<script type="text/javascript">
$(document).ready(function () {
$('#aDialog').dialog({
autoOpen: true,
height: 250, width: 400,
modal: true,
buttons: {
"Ok!": function () {
$(this).dialog("close");
}
}
});
});
</script>
<h2>Index</h2>
#using (Html.BeginForm("PostDialogField", "DialogField"))
{
#Html.ValidationSummary(true)
<fieldset>
#Html.HiddenFor(m => m.ID)
#Html.DisplayFor(m => m.message)
<div id="aDialog">
<h3>Fill in this message!</h3>
<div class="editor-field">
#Html.EditorFor(m => m.message)
</div>
</div>
<p><input type="submit" value="Submit Message" /></p>
</fieldset>
}
The Model:
using System;
namespace My.Models
{
public class DialogFieldModel
{
public int ID { get; set; }
public String message { get; set; }
public DialogFieldModel()
{
message = "Default";
}
}
}
The controller:
using System;
using System.Web;
using System.Web.Mvc;
using WellTrkd.Models;
namespace My.Controllers
{
public class DialogFieldController : Controller
{
public ActionResult Index()
{
DialogFieldModel dfm = new DialogFieldModel(); // set default message
return View(dfm);
}
[HttpPost]
public ActionResult PostDialogField(DialogFieldModel dfm)
{
String message = dfm.message;
if (message != "Default")
//Yay!
return RedirectToAction("Index");
else // Boo
return RedirectToAction("Index");
}
}
}
Unfortunately the #message field is never submitted along with the rest of the HTML POST parameters (checked in network tab of chrome dev view) unless I take it out of the dialog. The result is that in the PostDialogField action dfm.message contains the "Default" string, even if I've changed the message in the dialog.
I know I could add a hidden field to the form that is kept synchronized with the field in the dialog, but I feel I'm missing something. Any thoughts oh wise ones?
Your problem is that the element you turn into a dialog is moved out of the form towards a new dialog-element at the bottom of the DOM. And since it's not part of the form any more, it won't be submitted when the form is submitted.
If you'd destroy the dialog when closing it, it would be moved back to where it was, but I can't see if that's what is desired. The other option is to sync elements.