ASP.NET MVC validating model - c#

I am having some difficulties in validation of form.
Since I want to populate combobox from database (another table, I'm using viewbags to do this), is there a way to use ComboBoxFor in this case so I could use System.ComponentModel.DataAnnotations and jquery.validate.js?
View:
#model MyProject.OpenAccess.Document
#using (Html.BeginForm("CreateDocument", "Create", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<label>Select file:</label><br />
<input type="file" name="file" /><br />
<label>Filetype</label><br />
#Html.DropDownList("fileType", (IEnumerable<SelectListItem>)ViewBag.ListFiletypes, "-Filetypes", new { #class = "filetype-cb" }) <br />
<input type="submit" value="Add"/>
}
Controller:
public ActionResult CreateDocument(HttpPostedFileBase file, string fileType)
{
if (file != null && file.ContentLength > 0)
{
Document doc = new Document()
{
Filetype = fileType
}
if (this.TryValidateModel(doc))
{
this.dbContext.Add(doc);
this.dbContext.SaveChanges();
id = doc.ID;
//save document using document ID
}
}
}
EDIT:
Here is my current implementation of this:
Layout.cshtml
Remember to add:
<script src="#Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
View
#model MyProject.OpenAccess.Document
#using (Html.BeginForm("CreateDocument", "Create", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<label>Select file:</label><br />
<input type="file" data-val="true" data-val-required="Please select a file!" name="file" /><br />
<label>Filetype</label><br />
#Html.DropDownListFor(m => m.FileType, (IEnumerable<SelectListItem>)ViewBag.ListFiletypes, "", new { #class = "filetype-cb" })
<input type="submit" value="Add"/>
}
Controller
[HttpGet]
public ActionResult CreateDocument()
{
ViewBag.ListFiletypes = new SelectList(this.dbContext.ListFiletypes.ToList(), "FileType", "FileType");
//Populate dropdownlist from database
}
[HttpPost]
public ActionResult CreateDocument(HttpPostedFileBase file, Document doc)
{
if (file != null && file.ContentLength > 0)
{
if (this.TryValidateModel(doc))
{
this.dbContext.Add(doc);
this.dbContext.SaveChanges();
id = doc.ID;
//save document using document ID
}
}
}
Model
public partial class Document
{
private int _iD;
public virtual int ID
{
get
{
return this._iD;
}
set
{
this._iD = value;
}
}
private string _fileType;
[Required(ErrorMessage = "Required!")]
public virtual string FileType
{
get
{
return this._fileType;
}
set
{
this._fileType = value;
}
}
}
Is there good reasons to use Viewmodel over Viewbag to populate DropDownListFors (found this from http://www.codeproject.com/Articles/687061/Multiple-Models-in-a-View-in-ASP-NET-MVC-MVC)?
Only problem with this implementation is that I can't validate file on client-side (so user couldn't post empty file).
EDIT: Got this working by just adding:
<input type="file" data-val="true" data-val-required="Please select a file!" name="file" />

Normally you have 3 different parts, which build a proper implementation of your scenario.
The ViewModel:
In a proper implementation, you got for every view its own Viewmodel. In your case that would be something like this:
public class CreateDocumentViewModel
{
[Required]
public IEnumerable<SelectListItem> filetypes { get; set; }
// Maybe some more attributes you need in the view?
}
Note: here you can use the DataAnnotations you want.
The View:
The view contains the actual data in the dropdown.
#model CreateDocumentViewModel
#using (Html.BeginForm("CreateDocument", "Create", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<label>Select file:</label><br />
<input type="file" name="file" /><br />
<label>Filetype</label><br />
#Html.DropDownListFor(model => model.filetypes, model.filetypes, "-Filetypes", new { #class = "filetype-cb" })
<br />
<input type="submit" value="Add"/>
}
The controller:
The controller you need, has 2 actions. A GET- and a POST-action.
[HttpGet]
public ActionResult CreateDocument()
{
CreateDocumentViewModel model = new CreateDocumentViewModel();
model.filetypes = FillFileTypesFromDB; // Here you fill the data for the dropdown!
return View(model);
}
Finally you need the POST-action which saves back to your db.
[HttpPost]
public ActionResult CreateDocument(HttpPostedFileBase file, string fileType)
{
if (file != null && file.ContentLength > 0)
{
Document doc = new Document()
{
Filetype = fileType
}
if (this.TryValidateModel(doc))
{
this.dbContext.Add(doc);
this.dbContext.SaveChanges();
id = doc.ID;
//save document using document ID
}
}
}
I hope I fully understood your problem and it helps solving it.

Related

File Upload with Partial View MVC 5

I'm attempting to upload a number and a file through an Html-Helper form in my MVC 5 application. After submitting the model, it seems that my model.File property from the view model always returns null.
Before adding the model.File != null check, my application was throwing a NullReferenceException.
I have my form logic contained in a partial view called UploadFile.cshtml, since my form logic requires a different model than my index.cshtml.
How do I read the File property from my form in my [HttpPost] controller action?
index.cshtml
#Html.Action("UploadFile", "Home")
UploadFile.cshtml
#model FileViewModel
#using (Html.BeginForm("FormUpload", "Home", FormMethod.Post))
{
<div class="form-group">
#Html.TextBoxFor(m => m.Marker)
</div>
<div class="form-group">
#Html.TextBoxFor(m => m.File, new { type = "file" })
</div>
<input type="submit" name="submit" value="Submit" />
}
HomeController
public PartialViewResult UploadFile()
{
return PartialView("UploadFile", new FileViewModel());
}
// /Home/FormUpload
[HttpPost]
public ActionResult FormUpload(FileViewModel model)
{
if (ModelState.IsValid)
{
if (model.File != null && model.File.ContentLength > 0 && model.File.ContentType == "application/pdf")
{
// Convert file to byte[] and upload
// ...
ViewBag.Message = "File Uploaded Successfully";
}
else
{
ViewBag.Message = "File Not Uploaded";
}
}
return RedirectToAction("Index");
}
FileViewModel
public class FileViewModel
{
public int ID { get; set; }
public int Marker { get; set; }
public string Filename { get; set; }
public HttpPostedFileBase File { get; set; }
}
You are missing enctype attribute for the form, you need to specify that for file case:
#using (Html.BeginForm("FormUpload", "Home",
FormMethod.Post,
new { enctype = "multipart/form-data" }))
See this article for further details.
Html.BeginForm("FormUpload", "Home", FormMethod.Post))
should be
#using (Html.BeginForm("FormUpload", "Home", FormMethod.Post, new {enctype = "multipart/form-data"}))

Edit and display entity images

I want to be able to replace my old images with a new. I got one to many relationship between entites -> many images for one product.
If I change images, old are staying in database. They will be never used. After some time the database is able to make huge itself. How to delete these old files ?
public ActionResult Edit(int? id)
{
if (id == null)
{ return new HttpStatusCodeResult(HttpStatusCode.BadRequest); }
Product product = db.Products.Include(p => p.FilePaths).Where(i => i.ProductId == id).Single();
if (product == null)
{ return HttpNotFound(); }
return View(product);
}
[HttpPost]
public ActionResult Edit(int id, IEnumerable<HttpPostedFileBase> uploads)
{
var product = db.Products.Include(p => p.FilePaths).Where(p => p.ProductId == id).Single();
if (ModelState.IsValid)
{
try
{
product.FilePaths = new List<FilePath>();
foreach (var upload in uploads)
{
if (upload != null && upload.ContentLength > 0)
{
var photo = new FilePath
{
FileName = Path.GetFileName(upload.FileName),
FileType = FileType.Photo
};
product.FilePaths.Add(photo);
upload.SaveAs(Path.Combine(Server.MapPath("~/Content/Images"), photo.FileName));
}
}
db.Entry(product).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
catch (RetryLimitExceededException)
{
ModelState.AddModelError("", "Unable to save changes. Contact with administrator.");
}
}
return View(product);
}
My view for Edit() is:
#model Domain.Entities.Product
#using (Html.BeginForm("Edit", "Home", FormMethod.Post, new { enctype = "multipart/form-data" })) {
// code ommited for brevity..
<div class="row">
#Html.Label("Images:", new { #class = "control-label col-md-2" })
<div class="col-md-1">
<input type="file" name="uploads" id="upload1" />
</div>
<div class="col-md-offset-2 col-md-1">
<input type="file" name="uploads" id="upload2" />
</div>
<div class="col-md-offset-2 col-md-1">
<input type="file" name="uploads" id="upload3" />
</div>
</div>
//code ommited for brevity..
Also if somebody could tell me how to change this properly I would be grateful.
I am getting first image of product as many times as many images a product got. I want to display all images once.
I am in trouble with a directory to images.
<div class="row">
<label class="control-label col-md-2"> Obecne zdjęcia:</label>
#foreach (var item in Model.FilePaths)
{
<div class="col-md-offset-1 col-md-1">
<img src="~/Content/Images/#Model.FilePaths.FirstOrDefault(f => f.FileType == FileType.Photo).FileName" alt="" style="width:auto; height:100px">
</div>
}
</div>
My model class for FilePath:
public class FilePath
{
public int FilePathId { get; set; }
public string FileName { get; set; }
public FileType FileType { get; set; }
public int? ProductId { get; set; }
//navigation..
public virtual Product Product { get; set; }
}
Every help much appreciated. Thanks!

HttpPostedFileBase null when inside ViewModel

I searched the internet for possible problems that might be causing this and found nothing ...
When I put the IEnumerable<HttpPostedFileBase> FilesUploadedEvent as action parameter alongside ViewModel everything seems to be working fine.
[HttpPost]
public ActionResult Create(EventViewModel model, IEnumerable<HttpPostedFileBase> FilesUploadedEvent)
{
if (ModelState.IsValid)
{
...
Problem is when I try to put it inside object in my ViewModel
#model ViewModels.Event.EventViewModel
#using (Html.BeginForm("Create", "Event", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.ValidationSummary(true, null, new { #class = "validation-msg" })
#Html.EditorFor(m => m.BasicInfoSection)
...
<input type="submit" value="Add" />
}
Controller after removing 2nd parameter:
[HttpPost]
public ActionResult Create(EventViewModel model)
{
if (ModelState.IsValid)
{
...
ViewModel:
public class EventViewModel
{
public BasicInfoSection BasicInfoSection { get; set; }
...
And we put the parameter inside ViewModels object:
public class BasicInfoSection : IValidatableObject
{
public string Remarks { get; set; }
public IEnumerable<HttpPostedFileBase> FilesUploadedEvent { get; set; }
...
and here is editor template for BasicInfoSection :
#model ViewModels.Event.Parts.BasicInfoSection
<input id="FilesUploadedEvent" name="FilesUploadedEvent" type="file" data-role="upload" multiple="multiple" autocomplete="off">
#(Html.TextAreaFor(m => m.Remarks, new { #class = "form-control", style = "height:200px;" }))
...
Also if the form wont validate on some other fields is it possible to return Files in the postBack? I read it is not because of security reasons. Is Ajax validation the only method than?
The name attribute of the file input is not correct. Because FilesUploadedEvent is a property of BasicInfoSection, then in order to be bound on post back, the input would need to be
<input name="BasicInfoSection.FilesUploadedEvent" type="file" .. />
Note also how the textareahas the name attribute name="BasicInfoSection.Remarks"

Aggregate all my textbox values into a list before posting the form

I am building an MVC form with a bunch of textboxes. However, my model requires a list of strings. What I'm trying to do is have it so when the user hits submit, before the form posts, it takes all of the values in the text box, adds it to a List and appends it to the model for submitting. I'm stuck at how I would be able to do this?
The reason I need to do this is that I need the form to be dynamic in that the # of fields can grow and the page adapts to that. I can't hardcode the individual fields into the model as the model would need to adapt to the length of the form.
Here's my code so far:
The HTML
#using (Html.BeginForm(new { id = "feedbackForm" }))
{
<fieldset>
#foreach (Feedback_Num_Questions questionForm in FeedbackSurveyQuestions)
{
string textBoxID = "feedbackq";
questionNumberTextBoxes++;
textBoxID = textBoxID + questionNumberTextBoxes.ToString();
<input type="text" id="#textBoxID" />
}
<input type="text" id="txtAdditionalComments" />
<input type="submit" value="Create" name="btnFeedbackSubmit" id="btnSubmitFeedbackForm" />
</fieldset>
}
The Model:
public class PostCourseFeedbackModel
{
public List<string> responseList { get; set; }
public decimal pelID { get; set; }
public string employeeid { get; set; }
}
Thanks for the help!
UPDATE: after getting a response from FLEM, I revised my code a bit to this. Still not quite working, but was hoping somebody could spot out the issue:
HTML:
#using (Html.BeginForm(new { id = "feedbackForm" }))
{
<fieldset>
#{
int i = 0;
foreach (Feedback_Num_Questions questionForm in FeedbackSurveyQuestions)
{
i++;
string textBoxID = "feedbackq" + i.ToString();
#Html.TextBoxFor(m => m.responseList[i], new { #id = textBoxID });
}
<input type="submit" value="Create" name="btnFeedbackSubmit" id="btnSubmitFeedbackForm" />
}
</fieldset>
}
The model remained unchanged...
UPDATE - As requested by flem, I am posting my action. However this is a barebones action as I have yet to code what I plan to do with the data...
[HttpPost]
public ActionResult PostCourseFeedback(PostCourseFeedbackModel feedback)
{
return RedirectToAction("FeedbackSubmitted", "Feedback", new { status = "success" });
}
I would let the model binder do that hard work for you.
#using (Html.BeginForm(new { id = "feedbackForm" }))
{
<fieldset>
#{
int i = 0;
foreach (Feedback_Num_Questions questionForm in FeedbackSurveyQuestions)
{
#Html.EditorFor(m => m.responseList[i++])
}
}
<input type="text" id="txtAdditionalComments" />
<input type="submit" value="Create" name="btnFeedbackSubmit" id="btnSubmitFeedbackForm" />
</fieldset>
}
Here's a working sample:
Model and controller:
using System.Collections.Generic;
using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
public class MyController : Controller
{
public ActionResult MyAction(MyModel Model)
{
return View(Model);
}
}
public class MyModel
{
public List<string> TextFields { get; set; }
}
}
View:
#model MvcApplication1.Controllers.MyModel
#using (Html.BeginForm())
{
for (int i = 0; i < 3; i++)
{
string id = "txt" + i.ToString();
#Html.TextBoxFor(m => m.TextFields[i], new { id = id })
}
<input type="submit" value="Post" />
if (Model.TextFields != null)
{
foreach (string textField in Model.TextFields)
{
<div>Posted Field: #textField</div>
}
}
}
I believe that if you modify the id and the name attributes of your textboxes and your post action is expecting your model class as its parameter you should be able to do something like the following when you create your textboxes:
<input type="text" id="id="responseList_#textBoxID__" name="responseList[#textBoxID]" />

in ASP.NET MVC HttpPostedFileBase collection has only null items

This is my code:
#using (Html.BeginForm("UploadImages", "Administration", new { _id = Model.Album.AlbumID, enctype = "multipart/form-data" }, FormMethod.Post))
{
<input type="file" name="fileUpload" id="file1" /><br />
<input type="file" name="fileUpload" id="file2" /><br />
<input type="file" name="fileUpload" id="file3" /><br />
<input name="addPhoto" type="submit" value="Добавить фото" />
}
[Authorize]
[HttpPost]
public ActionResult UploadImages(int _id, IEnumerable<HttpPostedFileBase> fileUpload)
{
gb_albumdbEntities1 entityes = new gb_albumdbEntities1();
foreach (var file in fileUpload)
{
if (file == null) continue; // **<---fileUpload items is always null!**
string path = AppDomain.CurrentDomain.BaseDirectory + "Photos/";
if (Path.GetFileName(file.FileName) != null)
{
string filename = file.GetHashCode().ToString();
string fullpath = Path.Combine(path, filename);
file.SaveAs(fullpath);
entityes.Photos.AddObject(new Photo() { AlbumID = _id, PhotoUrl = #"http://site.ru/Photos/" + filename });
}
}
entityes.SaveChanges();
return RedirectToAction("AlbumEdit", new { id = _id });
}
fileUpload items is always null. Where is the problem?Oo
UPD: calculated result post url:
http://localhost:56193/Administration/UploadImages?_id=4&enctype=multipart%2Fform-data
your list if file inputs need to be numbered for model binding to work. In the simplest form your view should look something like this:
<html>
<head>
<title>Index</title>
</head>
<body>
<div>
#using (Html.BeginForm("Index", "Upload", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
for (int i = 0; i < 3; i++)
{
#Html.TextBox(string.Format("fileUpload[{0}]", i), null, new { type="file" })<br />
}
<input name="submit" type="submit" value="Go" />
}
</div>
</body>
</html>
and your controller:
public class UploadController : Controller
{
//
// GET: /Upload/
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(IEnumerable<HttpPostedFileBase> fileUpload)
{
return View();
}
}
Try the following post which I've used myself before. Worked for me.
http://haacked.com/archive/2010/07/16/uploading-files-with-aspnetmvc.aspx

Categories