HttpPostedFileBase Null in controller - Want to save file path to database - c#

I want to save the file path to my database reports table. I have a column of type: string FilePath.
The end goal is that I want to be able to download the file from a report details view. Obviously the report download link would be different depending on the report ID.
Currently it doesn't seem that the controller is receiving anything as before I had Object reference not set to an instance of an object exception. I then added file != null in my if statement so I don't get the error anymore. However clearly the underlying issue is still present. Here is my controller save action:
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize(Roles = "AdminManager")]
public ActionResult Save(Report report, HttpPostedFileBase file)
{
if (!ModelState.IsValid)
{
var viewModel = new ReportFormViewModel
{
Report = report,
Members = _context.Members.ToList(),
Subjects = _context.Subjects.ToList()
};
return View("ReportForm", viewModel);
}
if (file != null && file.ContentLength > 0)
{
string filePath = Path.Combine(
Server.MapPath("~/App_Data/Uploads"),
Path.GetFileName(file.FileName));
file.SaveAs(filePath);
}
if (report.Id == 0)
_context.Reports.Add(report);
else
{
var reportInDb = _context.Reports.Single(e => e.Id == report.Id);
reportInDb.Name = report.Name;
reportInDb.MemberId = report.MemberId;
reportInDb.SubjectId = report.SubjectId;
reportInDb.Date = report.Date;
reportInDb.FilePath = report.FilePath;
}
_context.SaveChanges();
return RedirectToAction("Index", "Report");
}
Here is my form view:
<h2>#Model.Title</h2>
#using (Html.BeginForm("Save", "Report", new {enctype = "multipart/form-data"}))
{
<div class="form-group">
#Html.LabelFor(r => r.Report.Name)
#Html.TextBoxFor(r => r.Report.Name, new { #class = "form-control" })
#Html.ValidationMessageFor(r => r.Report.Name)
</div>
<div class="form-group">
#Html.LabelFor(r => r.Report.Date) e.g. 01 Jan 2000
#Html.TextBoxFor(r => r.Report.Date, "{0: d MMM yyyy}", new { #class = "form-control" })
#Html.ValidationMessageFor(r => r.Report.Date)
</div>
<div class="form-group">
#Html.LabelFor(m => m.Report.MemberId)
#Html.DropDownListFor(m => m.Report.MemberId, new SelectList(Model.Members, "Id", "Name"), "Select Author", new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Report.MemberId)
</div>
<div class="form-group">
#Html.LabelFor(m => m.Report.SubjectId)
#Html.DropDownListFor(m => m.Report.SubjectId, new SelectList(Model.Subjects, "Id", "Name"), "Select Subject", new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Report.SubjectId)
</div>
<div class="form-group">
#Html.LabelFor(m => m.Report.FilePath)
<input type="file" name="file" id="file"/>
</div>
#Html.HiddenFor((m => m.Report.Id))
#Html.AntiForgeryToken()
<button type="submit" class="btn btn-primary">Save</button>
}
Current code doesn't seem to send file data to action.

It is recommended to add the file to your model:
public class Report {
[Required]
[Display(Name = "Report File")]
public HttpPostedFileBase ReportFile { get; set; }
//... The other fields
}
Usually I would append ViewModel, so ReportViewModel instead of Report. This makes it easier to distinguish between view models and business/data models.
And in your Razor:
<div class="form-group">
#Html.LabelFor(m => m.Report.ReportFile)
#Html.TextBoxFor(m => m.ReportFile, new { type = "file" })
<!--You can also use <input type="file" name="ReportFile" id="ReportFile"/>-->
</div>
Note that the name that you use in the LabelFor must match the ID of the control. In your code FilePath and file didn't match.
And finally in the controller:
public ActionResult Save(Report report)
{
//...some code
string filePath = Path.Combine(Server.MapPath("~/App_Data/Uploads"),
Path.GetFileName(report.ReportFile.FileName));
report.ReportFile.SaveAs(filePath);
//...other code
}
I wouldn't use the name of the uploaded file. Instead, I would give it a name according to my project's naming convention. I often use the ID as the name, perhaps with some prefix. Example:
var fileName = "F" + report.Id + ".jpg"; //You can get the extension from the uploaded file
string filePath = Path.Combine(Server.MapPath("~/App_Data/Uploads"), fileName);
Obviously, when you're inserting a new object, you won't have an ID until you insert it into the database, so the code to save the physical file must be placed after the code to insert it into the database. If you follow this logic, you don't need to save the path in the database, because the path can be always calculated from the ID. So you save a column in the database, gain performance in your code as you don't need to handle another string column, and you have a clear and simply file naming convention that is safe without user input risk.
Another way I follow, especially when the type of the file may vary (i.e. you can upload files with different extensions), is using a GUID for the file name. In this case, the file name must be saved in the database, but the GUID can be generated before inserting the object into the database. Example:
string ext = report.ReportFile.FileName.Substring(
report.ReportFile.FileName.LastIndexOf('.')).ToLower();
var fileName = Guid.NewGuid().ToString() + ext;
string filePath = Path.Combine(Server.MapPath("~/App_Data/Uploads"), fileName);

Related

ASP.net use session data to add something in create view

I created this simple ASP.NET project. (Default template using MVC)
In there I generated models from my database using ADO.net
I also generated controller for my model. (The model generated functions for create, edit, delete...) I also got view for every function in the controller.
So what I am trying to do now is:
-I am in my create view. (that means I see my form for creating objects)
-I need to enter data for [title, content] but to post in database I also need an id (this id is a foreign key, not the id of the object i am creating)
I already have this id saved in my session. I can access the session data by doing:
var user = Session["user"] as Uporabniki; //returns session data
user.id //selects id from session
Now what I want is to use this id in the create form textbox.
As of now the rows for id in my view look like this (I have no idea why it's a dropdown list. When I open the site I see names of all users in database and I can select one. But this is not what I want. I want to see only one):
<div class="form-group">
#Html.LabelFor(model => model.id_avtorja, "id_avtorja", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("id_avtorja", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.id_avtorja, "", new { #class = "text-danger" })
</div>
</div>
The create methods in controller look like this
// GET: Vprasanjas/Create
public ActionResult Create()
{
ViewBag.id_avtorja = new SelectList(db.Uporabniki, "id", "uporabniskoIme");
return View();
}
// POST: Vprasanjas/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "id,naslov,vsebina,datum,id_avtorja")] Vprasanja vprasanja)
{
if (ModelState.IsValid)
{
db.Vprasanja.Add(vprasanja);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.id_avtorja = new SelectList(db.Uporabniki, "id", "uporabniskoIme", vprasanja.id_avtorja);
return View(vprasanja);
}
Why is it not working if I change the view to something like this:
<div class="form-group">
#Html.LabelFor(model => model.id_avtorja, "id_avtorja", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#var user = Session["user"] as Uporabniki;
#Html.Raw(user.id)
#Html.ValidationMessageFor(model => model.id_avtorja, "", new { #class = "text-danger" })
</div>
</div>
And how can I fix this?
Try rewrite to
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "id,naslov,vsebina,datum,id_avtorja")] Vprasanja vprasanja)
{
if (ModelState.IsValid)
{
vprasanja.id = (Session["user"] as Uporabniki).id;
db.Vprasanja.Add(vprasanja);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.id_avtorja = new SelectList(db.Uporabniki, "id", "uporabniskoIme", vprasanja.id_avtorja);
return View(vprasanja);
}
Main idea - assign your id on post.
Please post some info on the error. But my guess is you aren't creating any input HTML element so there's nothing posting. You need something like <input type="hidden" id="id_avtorja" value="#user.id"> in the form.
Also, I'd advise against using data out of a session variable. That's older technology and very un-MVC in philosophy.

retrieve model properties values inside GET method in mvc

I have following GET method , that's a code to a create form
public ActionResult Add_Product(string Product_ID)
{
AddNewProduct sample = new AddNewProduct();
return View(sample);
}
this is the model class for that
public class AddNewProduct
{
public string Product_ID { get; set; }
...
}
this is that create form
#model project_name.Models.AddNewProduct
<h4>Add New Product</h4>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new { #class = "text-danger" }) <div class="form-group">
#Html.LabelFor(model => model.Product_ID, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(model => model.Product_ID, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Product_ID, "", new { #class = "text-danger" })
</div>
</div>
.....
<div>
#Html.ActionLink("Back to AddNewProduct", "AddNewProduct","Home" , new {Product_ID = Model.Product_ID})
</div>
}
I can Insert a Product_ID using this view page .But Once I click this Back to AddNewProduct link and debug AddNewProduct I cannot see any value for string Product_ID
Why this model properties not bind well
You need to assign value. Assign value of Product_ID which you are sending from get method to Product_ID property of class
public ActionResult Add_Product(string Product_ID)
{
AddNewProduct sample = new AddNewProduct();
sample.Product_ID = Product_ID;
return View(sample);
}
To pass the value of the textbox to the Add_Product() GET method, you need to use javascript/jquery. Replace you #Html.ActionLink(..) with
Back to AddNewProduct
and add the following script
var baseUrl = '#Url.Action("Add_Product", "Home")';
$('#back').click(function() {
var id = $('#Product_ID').val();
location.href = baseUrl + '/' + id;
}}
Note: location.href = baseUrl + '/' + id; assumes your have defined a specific route with {controller}/{action}/{Product_ID}, otherwise it needs to be
location.href = baseUrl + '?Product_ID=' + id;
Alternatively, change the method parameter to string id so it uses the default route
Note also that you will probably want to change the method to
public ActionResult Add_Product(string Product_ID)
{
AddNewProduct sample = new AddNewProduct
{
Product_ID = Product_ID
};
return View(sample);
}
so that if you click the Back to AddNewProduct link, the view will display the previous value you entered.
The second parameter of the #Html.ActionLink is the actionName but you sent the model name (AddNewProduct). Change it to this:
#Html.ActionLink("Back to AddNewProduct", "Add_Product","Home" , new {Product_ID = Model.Product_ID})
Or use this overload (You need to send null also when using this ActionLink overload):
#Html.ActionLink("Back to AddNewProduct", "Add_Product","Home" , new {Product_ID = Model.Product_ID}, null)

pass uploader file and form filed value to the one action in asp.net mvc

i have one form and one uploader (i use PLUploader) and want user fill textboxs and select image in PLUploader and when click on submit button , i pass image and textboxs value to one action , i write this code, but alwaye i get null in textboxs value but get image in action ,i think this problem related to call the one action with form and PLuploader,
public ActionResult Insert(News news, HttpPostedFileBase file)
{
// i get null in new but get file in HttpPostedFileBase
int result = 0;
HttpPostedFileBase FileData = Request.Files[0];
string fileName = null;
fileName = Path.GetFileName(FileData.FileName);
if (ModelState.IsValid)
{
//do some thing
}
else
{
return View(news);
}
}
#using (Html.BeginForm("Insert", "News", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<div class="col-xs-12">
#Html.LabelFor(model => model.NewsTitle)
#Html.TextBoxFor(model => model.NewsTitle, new { #class = "form-control",#name="title" })
#Html.ValidationMessageFor(model => model.NewsTitle)
</div>
<div class="col-xs-12">
<div id="uploader" class="img-plc">
<p>You browser doesn't have Flash, Silverlight, Gears, BrowserPlus or HTML5 support.</p>
</div>
<ul id="gallery"></ul>
</div>
<div class="col-xs-12">
#Html.LabelFor(model => model.NewsText, new { #class = "text-right" })
#Html.ValidationMessageFor(model => model.NewsText)
#Html.TextAreaFor(model => model.NewsText, new { #rows = "10", #cols = "80", #class = "text-editor", #name = "title" })
</div>
<button type="submit">Submit</button>
}
var uploader = $("#uploader").pluploadQueue({
// General settings
runtimes: 'html5,gears,flash,silverlight,browserplus,html4',
url: '#Url.Action("Insert", "News")',
max_file_size: '10mb',
chunk_size: '1mb',
unique_names: true,
multi_selection: false,
multiple_queues: false,
// Specify what files to browse for
filters: [
{ title: "Image files", extensions: "jpg,png" }
],
// Flash settings
flash_swf_url: '/Scripts/Moxie.swf',
// Silverlight settings
silverlight_xap_url: '/Scripts/Moxie.xap'
})
$('form').submit(function (e) {
var uploader = $('#uploader').pluploadQueue();
// Files in queue upload them first
if (uploader.files.length > 0) {
// When all files are uploaded submit form
uploader.bind('StateChanged', function () {
if (uploader.files.length === (uploader.total.uploaded + uploader.total.failed)) {
$('form')[0].submit();
}
});
uploader.start();
} else {
alert('You must queue at least one file.');
}
return false;
});
how i can fixed this ?
i want get news and file in this action
thank you
you're not submitting your whole form; instead you're capturing the submit action with the jQuery event handler for onSubmit:
$('form').submit(function (e) { ... });
If you want to do it this way, you'll have to serialize your News model inside this jQuery code block as well and post it along with the file upload.
I would just stick the HttpPostedFileBase inside the News model and use the normal form submit though.

pass file and model into one action asp.net

I have one form and one uploader (I use PLUploader) and want user fill textboxs and select image in PLUploader and when click on submit button,
I pass image and textboxs value to one action, I write this code, but always I get null in textboxs value but get image in action.
I think this problem related to call the one action with form and PLuploader.
public ActionResult Insert(News news, HttpPostedFileBase file)
{
// I get null in new but get file in HttpPostedFileBase
int result = 0;
HttpPostedFileBase FileData = Request.Files[0];
string fileName = null;
fileName = Path.GetFileName(FileData.FileName);
if (ModelState.IsValid)
{
//do some thing
}
else
{
return View(news);
}
}
#using (Html.BeginForm("Insert", "News", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<div class="col-xs-12">
#Html.LabelFor(model => model.NewsTitle)
#Html.TextBoxFor(model => model.NewsTitle, new { #class = "form-control",#name="title" })
#Html.ValidationMessageFor(model => model.NewsTitle)
</div>
<div class="col-xs-12">
<div id="uploader" class="img-plc">
<p>You browser doesn't have Flash, Silverlight, Gears, BrowserPlus or HTML5 support.</p>
</div>
<ul id="gallery"></ul>
</div>
<div class="col-xs-12">
#Html.LabelFor(model => model.NewsText, new { #class = "text-right" })
#Html.ValidationMessageFor(model => model.NewsText)
#Html.TextAreaFor(model => model.NewsText, new { #rows = "10", #cols = "80", #class = "text-editor", #name = "title" })
</div>
<button type="submit">Submit</button>
}
var uploader = $("#uploader").pluploadQueue({
// General settings
runtimes: 'html5,gears,flash,silverlight,browserplus,html4',
url: '#Url.Action("Insert", "News")',
max_file_size: '10mb',
chunk_size: '1mb',
unique_names: true,
multi_selection: false,
multiple_queues: false,
// Specify what files to browse for
filters: [
{ title: "Image files", extensions: "jpg,png" }
],
// Flash settings
flash_swf_url: '/Scripts/Moxie.swf',
// Silverlight settings
silverlight_xap_url: '/Scripts/Moxie.xap'
})
$('form').submit(function (e) {
var uploader = $('#uploader').pluploadQueue();
// Files in queue upload them first
if (uploader.files.length > 0) {
// When all files are uploaded submit form
uploader.bind('StateChanged', function () {
if (uploader.files.length === (uploader.total.uploaded + uploader.total.failed)) {
$('form')[0].submit();
}
});
uploader.start();
} else {
alert('You must queue at least one file.');
}
return false;
});
How can I fix this? I want to get news and file in this action.
Create a ViewModel to contain both properties
public class NewsViewModel {
public News News { get; set; }
public HttpPostedFileBase File { get; set; }
}
public ActionResult Insert(NewsViewModel model) {
/* ... */
}
When you create the view pass the ViewModel into the view. Make sure you use the right name for the input field to make it bind correctly:
#Html.TextBoxFor(model => model.File, new { type = "file" })
I would assume you might have to tell your script what name the file input shoul have.

File upload control to upload PDF only

I got a file control like
<div class="form-group">
#Html.LabelFor(m => m.File, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.File, new { type = "file" })
</div>
I want it to allow only PDF format files, so in my model, its like
[Display(Name = "Terms of Business")]
[Required, FileExtensions(Extensions=".pdf", ErrorMessage="Incorrect file format")]
public HttpPostedFileBase File { get; set; }
However, the control still allows to upload documents of any format, why ?
What did I missed out ?
Try regular expressions.
[RegularExpression(#"^(([a-zA-Z]:)|(\\{2}\w+)\$?)(\\(\w[\w].*))(.pdf|.PDF)$", ErrorMessage = "Incorrect file format")]
And make sure you have jquery.validate.js and jquery.validate.unobtrusive.js referenced on the page for enabling client side validation.
Maybe you were missed jquery validate js files.
Make sure these code were in BundleConfig.cs:
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.unobtrusive*",
"~/Scripts/jquery.validate*"));
And put this code into view layer:
#Scripts.Render("~/bundles/jqueryval")
You can try this code on button click event.
if (FileUpload1.HasFile)
{
if (FileUpload1.PostedFile.ContentType == "application/pdf")
{
Label1.Text = "File upload";
string path = "images/" + FileUpload1.PostedFile.FileName;
FileUpload1.SaveAs(Server.MapPath(path));
}
}

Categories