Upload in MVC4, got 2 parameters in my action but file is empty - forms

I'm trying to upload a file to a directory. The following code worked for me
CONTROLLER
[HttpPost]
public ActionResult Index(HttpPostedFileBase uploadFile)
{
//string path = #"C:\Users\thomas\Desktop";
if (uploadFile != null)
{
string filePath = Path.Combine(Server.MapPath("/files"), Path.GetFileName(uploadFile.FileName));
uploadFile.SaveAs(filePath);
}
return RedirectToAction("Index");
}
HTML PAGE
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<form action="/Post/Index" method="post" enctype="multipart/form-data">
<label for="uploadFile">Upload file: </label>
<input name="uploadFile" id="uploadFile" type="file" />
<input value="uploadFile" type="submit" />
</form>
Now i'm trying to implement this in a function where i create a message which is created by a model that is containing a message and an item class. When i submit the form the model is passed to my savecontroller but the file is null in my parameter controller.
HTML PAGE
Create new message
#model GeoCitytroopers.Models.MessageItemModel
#{
ViewBag.Title = "Create";
}
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Event</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Message.MessagePicture)
</div>
<div>
<label for="uploadFile">Upload file: </label>
<input name="uploadFile" id="uploadFile" type="file" />
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Message.MessagePicture)
#Html.ValidationMessageFor(model => model.Message.MessagePicture)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Item.ItemTitle)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Item.ItemTitle)
#Html.ValidationMessageFor(model => model.Item.ItemTitle)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Item.ItemDescription)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Item.ItemDescription)
#Html.ValidationMessageFor(model => model.Item.ItemDescription)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
CONTROLLER
[HttpPost]
public ActionResult Create(HttpPostedFileBase uploadFile, MessageItemModel ViewModel)
{
if (ModelState.IsValid)
{
Utility ut = new Utility();
Item viewItem = ViewModel.Item;
Message viewMessage = ViewModel.Message;
if (uploadFile != null)
{
string filePath = Path.Combine(Server.MapPath("/files"), Path.GetFileName(uploadFile.FileName));
uploadFile.SaveAs(filePath);
}
//ADD USER TO ITEM
viewItem = ut.AddUserToItem(viewItem);
//ADD ITEM
viewItem.ItemCreateddate = DateTime.Now;
//ADD DISTRICT TO ITEM
viewItem.DistrictID = ut.GetUserDistrict();
db.Items.Add(viewItem);
//ADD LINK
viewMessage.Item = viewItem;
db.Messages.Add(viewMessage);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(ViewModel);
}
How can i pass the uploading file to my controller?
Thanks in advance!

You forgot set the correct enctype to the form. You cannot upload files without that:
#using (Html.BeginForm(null, null, FormMethod.Post, new { enctype = "multipart/form-data" })) {
...
}
Now the upload will work and your uploadFile parameter will not be null.

My initial guess is that the you created using Html helper doesn't have the necessary encrypt on it.
try using
using(#Html.BeginForm("action-name","controller-name",
FormMethod.Post, new { enctype="multipart/form-data"}){
}
with appropriate values for action-name and controller-name

Related

How to implement validation rules for elements from a partial view

We are developing a .net core 3.1 MVC application (actual with MVVMC).
We have implemented custom validation attributes. And want them to be checked on server and on client side.
The view is a standard view, however the user has the possibility to add multiple partial views to the standard view (via a button).
In the partial view we were not able to use the 'asp-for' tag helper within the input fields, because one can have several times the same item added, and we need to be able to create a list in the ViewModel out of those additional partial views selected. That's why we tried to build the tags by ourselfes.
Once a partial view has been requested via ajax we are calling the validator again. Server validation works, client validation only works for those inputs, which are on the main view, not for the inputs on the partial views. However, after refreshing or when the server sends the user back after a validation error, client validation starts working (because site is completely refreshed and jquery validation takes input fields of partial views into account).
Please find the code below:
Implementation of validation attribute
public class AllowedExtensionsAttribute: ValidationAttribute, IClientModelValidator
{
private readonly List<string> _extensions = new List<string>();
public AllowedExtensionsAttribute(string extensions)
{
_extensions.Add(extensions);
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
if (value is IFormFile file)
{
var extension = Path.GetExtension(file.FileName);
if (!_extensions.Contains(extension.ToLower()))
{
return new ValidationResult(ErrorMessage);
}
}
return ValidationResult.Success;
}
public void AddValidation(ClientModelValidationContext context)
{
context.Attributes.Add("data-val", "true");
context.Attributes.Add("data-val-extension", ErrorMessage);
}
}
MainViewModel
[AllowedExtensions(".pdf", ErrorMessage = "This data type is not allowed!")]
public IFormFile PdfDocumentOne { get; set; }
PartialViewModel
[AllowedExtensions(".pdf", ErrorMessage = "This data type is not allowed!")]
public IFormFile PdfDocumentTwo { get; set; }
Main View
<form method="post" asp-controller="Home" asp-action="MainView" enctype="multipart/form-data">
<div class="form-group top-buffer">
<div class="row">
<div class="col-2">
<label asp-for="PdfDocumentOne" class="control-label"></label>
</div>
<div class="col-3">
<input asp-for="PdfDocumentOne" class="form-control-file" accept="application/pdf" />
<span asp-validation-for="PdfDocumentOne" class="text-danger"></span>
</div>
</div>
</div>
<div id="container">
<div id="containerFull" class="form-group">
<div class="row">
<div class="col-2">
<label class="control-label">...</label>
</div>
<div class="col-10">
<div id="containerPartialView">
</div>
</div>
</div>
<div class="row">
<div class="col-2">
</div>
<div class="col-3">
<button id="AddPartialView" type="button" class="form-control">...</button>
</div>
</div>
</div>
</div>
...
<div class="form-group top-buffer">
<div class="row">
<div class="col-2">
<input type="submit" value="Submit" class="form-control" id="checkBtn" />
</div>
</div>
</div>
</form>
Partial View
<input id="Lists[#Model.ViewId].ViewId" name="Lists[#Model.ViewId].ViewId" class="partialViewModel" type="hidden" value="#Model.ViewId" />
<div id="Lists[#Model.ViewId].MainContainer" class="partialView">
<div class="form-group">
<div>
<div class="col-2">
<label asp-for="PdfDocumentTwo" class="control-label"></label>
</div>
<div class="col-3">
<input name="Lists[#Model.ViewId].PdfDocumentTwo" id="Lists[#Model.ViewId].PdfDocumentTwo " type="file" class="form-control-file" accept="application/pdf"
data-val="true" data-val-extension="This data type is not allowed!"/>
<span class="text-danger field-validation-valid" data-valmsg-for="Lists[#Model.ViewId].PdfDocumentTwo" data-valmsg-replace="true"></span>
</div>
</div>
</div>
...
</div>
Javascript
function AddPartialView() {
var i = $(".partialView").length;
$.ajax({
url: '/Home/AddPartialView?index=' + i,
success: function (data) {
$('#containerPartialView').append(data);
$().rules('remove','extension');
jQuery.validator.unobtrusive.adapters.addBool("extension");
},
error: function (a, b, c) {
console.log(a, b, c);
}
});
}
$('#AddPartialView').click(function () {
AddPartialView();
});
jQuery.validator.addMethod("extension",
function (value, element, param) {
var extension = value.split('.').pop().toLowerCase();
if ($.inArray(extension, ['pdf']) == -1) {
return false;
}
return true;
});
jQuery.validator.unobtrusive.adapters.addBool("extension");
HomeController
[HttpPost]
public IActionResult MainView(MainViewModel vm)
{
if (vm == null)
{
return RedirectToAction("Index");
}
DatabaseHelper.GetMainViewModel(vm);
if (!ModelState.IsValid)
{
#ViewData["StatusMessageNegative"] = "The entered data is not valid. Please scroll down to correct your data.";
return View(vm);
}
return RedirectToAction("UploadDocument", new { Id = vm.Id});
}
[HttpGet]
public ActionResult AddPartialView(int index)
{
PartialViewModel pvm = DatabaseHelper.GetPartialViewModel(index);
return PartialView("Partial", pvm);
}
After searching in the internet we found the following argument (data-ajax="true"). However, we don't know where to place it or how to use it correctly.
Have you tried re-apply validation after loading your partial view?
$.ajax({
url: '/Home/AddPartialView?index=' + i,
success: function (data) {
$('#containerPartialView').append(data);
$().rules('remove','extension');
jQuery.validator.unobtrusive.adapters.addBool("extension");
},
error: function (a, b, c) {
console.log(a, b, c);
}
}).then(function () {
$("form").each(function () { $.data($(this)[0], 'validator', false); });
$.validator.unobtrusive.parse("form");
});

Trailing dot trouble while saving MVC 5

I have this model item to modify and save in MVC 5.
(.NET Framework 4.6.1)
#using (Ajax.BeginForm("Edit", new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "wrapperViews" }))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>EQUIPMENT - #ViewBag.EQP_ID</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.ID)
#Html.HiddenFor(model => model.OPERATIONID)
<div class="form-group">
#Html.LabelFor(model => model.DESCRIPTION, htmlAttributes: new { #class = "control-label col-md-4" })
<div class="col-md-8">
#Html.EditorFor(model => model.DESCRIPTION, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.DESCRIPTION, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.TYPE, htmlAttributes: new { #class = "control-label col-md-4" })
<div class="col-md-8">
#Html.DropDownListFor(model => model.TYPE,
new SelectList(Model.EquipmentTypes, "CodeType", "DescriptionType"),"", new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.TYPE, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div style="width:500px;margin-top:15px">
<div style="width:120px; float:left;margin-left:30px">
<input type="submit" value="Save" class="btn btn" />
</div>
<div style="width:120px; float:left;">
#Ajax.ActionLink("Back to the list", "Index", "Equipment", new { id = Model.OPERATIONID }, new AjaxOptions()
{
OnSuccess = "OpenEquipment"
})
</div>
</div>
</div>
</div>
The problem is that entity field to modify contains dot in the name, like that:
http://localhost:62396/controllername/Edit/SUPREP.ABL
When I submit the form, it comes error
HTTP Error 404.0 - Not Found
The resource you are looking for has been removed, had its name changed, or is temporarily unavailable.
Detailed Error Information:
Module IIS Web Core
Notification MapRequestHandler
Handler StaticFile
Error Code 0x80070002
Requested URL http://localhost:62396/controller/Edit/SUPREP.ABL
Physical Path C:\projectname\controller\Edit\SUPREP.ABL
I tried anything I could but I didn't solved my trouble.
Any help would be appreciated.
Just Solved. Need to add to RouteConfig.cs the following line:
routes.AppendTrailingSlash = true;
And everything works. Thank you.
The problem is with your URL. IIS thinks that you're requesting for a file with extension ".ABL". You can simply add a trailing / to make it act like a route.
e.g.: http://localhost:62396/controllername/Edit/SUPREP.ABL/
Also make sure you've enabled double escaping by adding this to web.config :
<security>
<requestFiltering allowDoubleEscaping="true"/>
</security>

Cannot add new role in MVC5 Microsoft.AspNet.Identity.EntityFramework.IdentityRole?

I add 3 roles using my MVC application before. Now I cant add new role. When I debug I can see new role Id but the Role name is empty. How can I solve this problem?
I have 3 roles at the moment. User, Admin, Sales. Now I want to add Account role and cannot add.
CONTROLLER
// POST: /Roles/Create
[HttpPost]
public ActionResult Create(FormCollection collection)
{
try
{
context.Roles.Add(new Microsoft.AspNet.Identity.EntityFramework.IdentityRole()
{
Name = collection["RoleName"]
});
context.SaveChanges();
ViewBag.ResultMessage = "Role created successfully !";
return RedirectToAction("Index");
}
catch
{
return View();
}
}
CSHTML
#model Microsoft.AspNet.Identity.EntityFramework.IdentityRole
<div class="container body-content">
#{
ViewBag.Title = "Create";
}
<h2>Create Role</h2>
#Html.ActionLink("List Roles", "Index") | #Html.ActionLink("Manage User Role", "ManageUserRoles")
<hr />
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<p>
<div class="form-group">
#Html.LabelFor(model => model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Name)
</div>
</div>
</p>
<br />
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</div>
}
</div>
You should only use viewModels in your view, but as you are using your view and object now, you should adjust your controller the following to use mvc roleManager (much easier):
// POST: /Roles/Create
[HttpPost]
public ActionResult Create(IdentityRole role)
{
try
{
var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(new ApplicationDbContext()));
roleManager.Create(role)
context.SaveChanges();
ViewBag.ResultMessage = "Role created successfully !";
return RedirectToAction("Index");
}
catch
{
return View();
}
}

ASP.NET MVC - DropDownList Validation Problem

I've got two DropDownLists in a form on a page that contain the same values (a list of languages). I want to ensure that the user doesn't select the same value in each of the drop downs.
I've tried using JavaScript to ensure the selected values aren't the same, it works fine but the form submits anyway.
What's the best way to accomplish this?
Here's the code from my View:
<script type="text/javascript">
function CheckLanguageDDL()
{
var form = document.getElementById("form0");
var sourceLangIndex = form.SourceLanguage.selectedIndex;
var targetLangIndex = form.TargetLanguage.selectedIndex;
var strSourceLanguage = form.SourceLanguage.options[sourceLangIndex].text;
var strTargetLanguage = form.TargetLanguage.options[targetLangIndex].text;
if (strSourceLanguage == strTargetLanguage)
{
alert("Source Language and Target Language must be different!");
return;
}
}
</script>
<% Html.BeginForm("Index", "Translate", FormMethod.Post, new { enctype = "multipart/form-data" }); %>
<fieldset>
<legend>Request</legend>
<br />
<div class="editor-label">
<%: Html.LabelFor(m => m.SourceLanguage) %>:
</div>
<div class="editor-field">
<%: Html.DropDownList("SourceLanguage", (IEnumerable<SelectListItem>)ViewData["SourceLanguages"]) %>
<%: Html.ValidationMessageFor(m => m.SourceLanguage) %>
</div>
<br />
<div class="editor-label">
<%: Html.LabelFor(m => m.TargetLanguage) %>:
</div>
<div class="editor-field">
<%: Html.DropDownList("TargetLanguage", (IEnumerable<SelectListItem>)ViewData["TargetLanguages"]) %>
<%: Html.ValidationMessageFor(m => m.TargetLanguage) %>
</div>
<input type="submit" value="Submit Request" onclick="CheckLanguageDDL();" />
</p>
</fieldset>
Thanks.
Make the function return a true/false value that the form submit use that return value
function CheckLanguageDDL()
{
var form = document.getElementById("form0");
var sourceLangIndex = form.SourceLanguage.selectedIndex;
var targetLangIndex = form.TargetLanguage.selectedIndex;
var strSourceLanguage = form.SourceLanguage.options[sourceLangIndex].text;
var strTargetLanguage = form.TargetLanguage.options[targetLangIndex].text;
if (strSourceLanguage == strTargetLanguage)
{
return false;
}
return true;
}
And on the button:
onclick="return CheckLanguageDDL();"

Empty model when submitting asp.net mvc 2 form

I gotta the following code in controller and view. The problem is that the model(Photo is an Entity Framework entity) is empty(all fields are nulled). Why?
// GET: /Admin/Photo/Create
public ActionResult Create()
{
return View();
}
//
// POST: /Admin/Photo/Create
[HttpPost]
public ActionResult Create(int id, FormCollection collection)
{
try
{
var file = (HttpPostedFileBase) Request.Files[0];
if (file != null && file.FileName != null)
{
var filename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Photos/Product/", Path.GetFileName(file.FileName));
file.SaveAs(filename);
var photo = new Photo();
photo.Description = collection["Description"];
photo.Main = collection["Main"].Contains("true");
photo.Filename = Path.GetFileName(file.FileName);
photo.Product_Id = id;
Entities.AddToPhotos(photo);
Entities.SaveChanges();
}
else
{
ModelState.AddModelError("", "Plik musi zostać załadowany.");
return View();
}
return RedirectToAction("Index");
}
catch
{
return View();
}
}
<h2>Create</h2>
<% using (Html.BeginForm(null, null, null, FormMethod.Post, new {enctype = "multipart/form-data" })) {%>
<%: Html.ValidationSummary(true) %>
<fieldset>
<legend>Fields</legend>
<div class="editor-label">
<%: Html.LabelFor(model => model.Description) %>
</div>
<div class="editor-field">
<%: Html.TextBoxFor(model => model.Description) %>
<%: Html.ValidationMessageFor(model => model.Description) %>
</div>
<div class="editor-label">
<label for="MainContent_file">Plik: </label>
</div>
<div class="editor-field">
<asp:FileUpload ID="file" runat="server" />
</div>
<div class="editor-label">
<%: Html.LabelFor(model => model.Main) %>
</div>
<div class="editor-field">
<%: Html.CheckBoxFor(model => model.Main) %>
<%: Html.ValidationMessageFor(model => model.Main) %>
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<% } %>
<div>
<%: Html.ActionLink("Back to List", "Index") %>
</div>
Update: I checked and the collection is populated with proper fields, but they are all nulled.
Check to make sure that the name attributes in the resulting html match your collection name. You could also change your public ActionResult Create(int id, FormCollection collection) to public ActionResult Create(int id, YourViewModel model) to automagically map the post values to the model.
Check the html source in the browser.
It might be sending them as: "Photo.Description"