MVC File upload with Custom Validation is null - asp.net

I am uploading and image and verifying its validity as per this post here:
How to validate uploaded file in ASP.NET MVC?
However, my example differs slightly because I am not just receiving the file, I am also receiving some properties for my model. However, my validator always fires, I debugged and found that my file is always null, so validator always fires back 'false'. I don't understand why, my input in view seems to be correct. Any ideas?
namespace PhotoManagement.Models
{
public class Photo
{
public virtual int PhotoId { get; set; }
public virtual int ClientId { get; set; }
public virtual string PhotoDescription { get; set; }
[ImageValidation(ErrorMessage="Please select a PNG/JPEG image smaller than 10 MB")]
[NotMapped]
public HttpPostedFileBase File { get; set; }
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Photo photo)
{
if (ModelState.IsValid)
{
db.Photos.Add(photo);
db.SaveChanges();
// File upload occurs now
var FilePath = Path.Combine(Server.MapPath("~/App_Data/" + photo.ClientId), photo.PhotoId.ToString());
photo.File.SaveAs(FilePath);
return RedirectToAction("Create");
}
else return View();
}
#using (Html.BeginForm(new { enctype = "multipart/form-data" })) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Photo for #Session["Name"]</legend>
<div class="editor-field">
#Html.Hidden("ClientId",(int)Session["UserId"])
</div>
<div class="editor-label">
#Html.LabelFor(model => model.PhotoDescription)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.PhotoDescription)
#Html.ValidationMessageFor(model => model.PhotoDescription)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.File)
</div>
<div class="editor-field">
<input type="file" name="File" id="File"/>
#Html.ValidationMessageFor(model => model.File)
</div>

You are using a wrong overload of the Html.BeginForm helper.
The correct call is this:
#using (Html.BeginForm(null, null, FormMethod.Post, new { enctype = "multipart/form-data" }))
{
}
You were calling:
Html.BeginForm(object routeValues)
instead of:
Html.BeginForm(
string actionName,
string controllerName,
FormMethod method,
object htmlAttributes
)
Look at the generated markup in your browser and you will see the fundamental difference.

Instead of
public ActionResult Create(Photo photo)
Try
public ActionResult Create(Photo photo, HttpPostedFileBase file)
EDIT: Don't forget to set the HTTP method to POST in the view:
#using (Html.BeginForm("ActionName", "ControllerName", FormMethod.Post, new { enctype = "multipart/form-data" }))

The file in Model will always give you null. In order to get file :
[HttpPost]
public ActionResult Create(UserViewModel model,
FormCollection formCollection, HttpPostedFileBase file){
/* Your code here */
if(file==null)
{
ModelState.AddModelError("NoFile", "Upload File");
}
}
Here HttpPostedFileBase file will give you the complete object of file uploaded. You can have check condition on the object file. Don't forget to add the below mentioned validation message in view.
#Html.ValidationMessage("NoFile")

Related

How to incorporate another controller's view and behavior into "this" controller's view?

I have a jQueryUI tabbed html page, and in its content area for one of the tabs, I have put as follows:
<div id="tabs-1ua">
#RenderPage("~/Views/Admin/Create.cshtml")
</div>
The Create.cshtml page does correctly appear within my tab, however when I create the user (this view is a basic user creation page) and click the button, nothing happens. No user is created and no error is presented. The "this" html with the tabs is in a different controller which does not have any model associations. The user creation is inside the AdminController, pertinent methods shown below:
public ActionResult Create()
{
return View();
}
[HttpPost]
public async Task<ActionResult> Create(CreateModel model)
{
if (ModelState.IsValid)
{
AppUser user = new AppUser { UserName = model.Name, Email = model.Email};
IdentityResult result = await UserManager.CreateAsync(user,
model.Password);
if (result.Succeeded)
{
return RedirectToAction("Index");
}
else
{
AddErrorsFromResult(result);
}
}
return View(model);
}
I put a breakpoint at the beginning of the Post method, but it was never hit when I accessed the create page from within my other page.
When I access this page directly and create a user, I get the expected behavior for new creation and validation. The model is as follows:
public class CreateModel
{
[Required]
public string Name { get; set; }
[Required]
public string Email { get; set; }
[Required]
public string Password { get; set; }
}
And the Create.cshtml view is as follows:
#model IdentityDevelopment.Models.CreateModel
#{ ViewBag.Title = "Create User";}
<h2>Create User</h2>
#Html.ValidationSummary(false)
#using (Html.BeginForm())
{
<div class="form-group">
<label>Name</label>
#Html.TextBoxFor(x => x.Name, new { #class = "form-control" })
</div>
<div class="form-group">
<label>Email</label>
#Html.TextBoxFor(x => x.Email, new { #class = "form-control" })
</div>
<div class="form-group">
<label>Password</label>
#Html.PasswordFor(x => x.Password, new { #class = "form-control" })
</div>
<button type="submit" class="btn btn-primary">Create</button>
#Html.ActionLink("Cancel", "Index", null, new { #class = "btn btn-default" })
}
My questions are, is it possible to do what I am trying to do? If so what changes do I need to make in order to reuse the existing available code?
Thank you.
You may explcitly specify which action method the form should post to when submit button is clicked.
You can use this overload of Html.BeginForm method to do so.
public static MvcForm BeginForm(
this HtmlHelper htmlHelper,
string actionName,
string controllerName
)
So update your Create view.
#model IdentityDevelopment.Models.CreateModel
#using (Html.BeginForm("Create","Admin"))
{
#Html.TextBoxFor(x => x.Name, new { #class = "form-control" })
<button type="submit" class="btn btn-primary">Create</button>
}
Now nomatter where you include this view, it will always post to Admin/Create
You should move your create form into a partial view which you can then render with RenderPartial. Then in your parent html page form do an ajax post to the partial views controller that defines the create method. Then you can use your partial anywhere you like with the logic centralized into the partial views controller.

ASP.NET code snippit to query database for unique field value

I'm trying to create custom remote data annotation to check for unique values.
So far I have:
[Remote("checkForUniqueSpeciesName", "Create", ErrorMessage = "A Species by that name already exists.")]
public string SpeciesName { get; set; }
and
public ActionResult checkForUniqueSpeciesName(string species_name)
{
bool is_unique = ........
return Json(is_unique, JsonRequestBehavior.AllowGet);
}
To be honest, I don't really understand how this works, I'm just trying to follow examples found on the web. I guess checkForUniqueSpeciesName is called when the form is submitted, and the method returns true or false. Is there something I need to put in the view to make the validation message come up, such as?
#Html.ValidationMessageFor(model => model.SpeciesName, "", new { #class = "text-danger" })
Do I need that?
Model Species.cs:
public class Species
{
[Key]
public int SpeciesId { get; set; }
[Display(Name = "Species")]
[Required(ErrorMessage = "You must enter a species name.")]
[Remote("CheckForUniqueSpeciesName", "Create", ErrorMessage = "A Species by that name already exists.")]
public string SpeciesName { get; set; }
}
Controller SpeciesController.cs:
namespace Gators3.Controllers
{
public class SpeciesController : Controller
{
private GatorsContext db = new GatorsContext();
// GET: Species
public ActionResult Index()
{
return View(db.Species.ToList());
}
// GET: Species/Create
public ActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "SpeciesId,SpeciesName")] Species species)
{
if (ModelState.IsValid)
{
db.Species.Add(species);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(species);
}
public ActionResult CheckForUniqueSpeciesName(string speciesName)
{
using (GatorsContext ctx = new GatorsContext())
{
bool isUnique = !ctx.Species.Any(s => s.SpeciesName == speciesName);
return Json(isUnique, JsonRequestBehavior.AllowGet);
}
}
.
.
.
.
View Views->Species->Create.cshtml:
#model Gators3.Models.Species
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Species</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.SpeciesName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.SpeciesName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.SpeciesName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
I guess checkForUniqueSpeciesName is called when the form is
submitted, and the method returns true or false.
No, that is not the case. The [RemoteAttribute] adds some JavaScript to your page automatically that will call a method on your Controller to do some server side validation and display the result on the page without the user needing to submit the whole HTML form. i.e. The validation is invoked when you tab out of the text box, not when you click submit.
With your code, I assume your controller is named CreateController?
I'm guessing you're just missing your data access code to actually check uniqueness?
So something like this would be required:
public ActionResult CheckForUniqueSpeciesName(string speciesName)
{
using (YourEntityFrameworkDbContext ctx = new YourEntityFrameworkDbContext())
{
bool isUnique = !ctx.Species.Any(s => s.SpeciesName == speciesName);
return Json(isUnique , JsonRequestBehavior.AllowGet);
}
}
Then in your view, you just need something like this:
#Html.ValidationMessageFor(x => x.SpeciesName)
Which will display the validation message you specified in your [Remote] attribute.
By the way, just as a side note - the coding conventions/casing you've applied to some of your code won't be popular with most C# programmers (unless your team are abiding by an unusual standard) so note the formatting I've applied.
Update - I think your code needs to have the following:
[Remote("CheckForUniqueSpeciesName", "Species", ErrorMessage="A Species by that name already exists.")]

#Html.ValidationMessageFor displays error message before user input

I have the following code:
In the model:
public class Student {
[Required(ErrorMessage = "name is a required field")]
public string Name { get; set; }
[Required(ErrorMessage = "school is a required field")]
public string School { get; set; }
}
In the controller:
public ActionResult StudentView()
{
return View();
}
[HttpPost]
public ActionResult StudentViewPost(Student model)
{
if(ModelState.IsValid)
{
....
....
}
}
And in my view, i have:
#using (Html.BeginForm()){
#Html.TextBoxFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
#Html.TextBoxFor(model => model.School)
#Html.ValidationMessageFor(model => model.School)
}
But when i go to the view page, the validation error messages are already displayed (on load), even before i get a chance to enter any input. Is there any reason why this could be happening? Could .NET be seeing this GET page as a POST page on load somehow and therefore display the error message? I'm not sure why this is happening and any thoughts/ideas would be great.
I have seen some issues in your Html.Beginform()
Normally you should write this begin blog as follow,
Example if your controller name is Student then,
#using (Html.BeginForm("StudentViewPost", "Student", FormMethod.Post))
{
#Html.TextBoxFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
#Html.TextBoxFor(model => model.School)
#Html.ValidationMessageFor(model => model.School)
}
You have not specifed the form method which will invoke the validations on Action
use following syntax
#using (Html.BeginForm("ActionName", "Controller", FormMethod.Post))
{
}

ASP.NET MVC redirect actions error with 'create' partial view on 'index' page

I'm having a problem with partial views. I have an index view of Announcements and I'm trying to add a partial view to create a new Announcement within the same page.
I can display the partial view, and submit the form to create a new record. The record gets submitted into the database, but when re-rendering the page, I get the error: Error executing child request for handler 'System.Web.Mvc.HttpHandlerUtil+ServerExecuteHttpHandlerAsyncWrapper', {"Child actions are not allowed to perform redirect actions."} on my Html.Action statement in my index page.
I've been struggling to make this work, and have firstly changed the Html.Partial to a Html.Action statement as the controller methods weren't firing, then secondly, after I read that this error is because while rendering the page, .NET doesn't know what my redirect action is doing so automatically stops it, tried changing the Html.Action to Html.RedirectAction inside a code block, but still get the same error detailed above.
My model is quite simple:
public class Announcement
{
public Announcement()
{
AnnouncementDate = System.DateTime.Now;
}
[Key]
public int AnnouncementID { get; set; }
public string Title { get; set; }
public string Type { get; set; }
}
My Controller methods:
public ViewResult Index(string searchString, int? page)
{
var Announcements = from a in db.Announcements
select a;
if (!String.IsNullOrEmpty(searchString))
{
Announcements = Announcements.Where(s => (s.Title.ToUpper().Contains(searchString.ToUpper()) || s.AnnouncementText.ToUpper().Contains(searchString.ToUpper())));
}
Announcements = Announcements.OrderBy(s => s.Title);
int pageSize = 10;
int pageNumber = (page ?? 1);
return View(Announcements.ToPagedList(pageNumber, pageSize));
}
//
// GET: /Announcement/Create
public ActionResult Create()
{
Announcement announcement = new Announcement();
return PartialView(announcement);
}
//
// POST: /Announcement/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Announcement announcement)
{
if (ModelState.IsValid)
{
db.Announcements.Add(announcement);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(announcement);
}
Index.cshtml
#model PagedList.IPagedList<Project.Models.Announcement>
#using PagedList.Mvc;
#using PagedList;
#using (Html.BeginForm())
{
#Html.TextBox("SearchString", ViewBag.CurrentFilter as string, new { #class = "search-query", placeholder = "Search by name" })
<input type="submit" value="Search" class="btn" />
}
#item.Title
#item.Type
#Html.Action("Create"); // This is the line causing errors after I submit the Create form. Have tried changing to Html.RedirectAction
Create.cshtml:
#model Project.Models.Announcement
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.TextBoxFor(model => model.Title, new { #style = "width:250px" })
#Html.TextBoxFor(model => model.Type, new { #style = "width:250px" })
<input type="submit" value="Create" class="btn btn-small" />
}
After doing some testing locally...
You can keep
#Html.Action("Create")
However, you have to change one small thing. Define what action the POST points to in your form :)
#model Project.Models.Announcement
#using (Html.BeginForm("Create"))
{
#Html.AntiForgeryToken()
#Html.TextBoxFor(model => model.Title, new { #style = "width:250px" })
#Html.TextBoxFor(model => model.Type, new { #style = "width:250px" })
<input type="submit" value="Create" class="btn btn-small" />
}

Asp.net MVC 3 "parameter conversion from type 'System.String' to type failed" when using SelectList Dropdown box

I'm stuck and after looking this up for hours, I think I need more eyeballs.
The situation is the following:
It's an Asp.Net MVC3 with Entity Framework 4 project. And I have two classes. One ConfigurationFile and another one Action. There is a one-to-many relationship between the two. Here is a simplified view on the code:
public class ConfigurationFile
{
[Key, Required]
[Column(TypeName = "uniqueidentifier")]
public Guid Id { get; set; }
[Required]
public string Name { get; set; }
[Column(TypeName = "uniqueidentifier")]
[Required]
public Guid ActionId { get; set; }
public virtual Models.Action Action { get; set; }
}
public class Action
{
[Key, Required]
[Column(TypeName = "uniqueidentifier")]
public Guid Id { get; set; }
[Required]
public string ActionValue { get; set; }
}
Then I want to create a new ConfigurationFile, and are my two controller methods (and at this point, this is 95% Visual Studio 10 generated code):
// db is my context class.
//
// GET: /Configuration/Create
public ActionResult Create()
{
ViewBag.ActionId = new SelectList(db.Actions, "Id", "ActionValue");
return View();
}
//
// POST: /Configuration/Create
[HttpPost]
public ActionResult Create(Models.ConfigurationFile configurationfile)
{
if (ModelState.IsValid)
{
configurationfile.Id = Guid.NewGuid();
db.ConfigurationFiles.Add(configurationfile);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.ActionId = new SelectList(db.Actions, "Id", "ActionValue", configurationfile.ActionId);
return View(configurationfile);
}
And here is a snippet of my Create view:
#model MyProject.Areas.ConfigurationFile.Models.ConfigurationFile
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Configuration File</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.ActionId, "Action")
</div>
<div class="editor-field">
#Html.DropDownList("ActionId", String.Empty)
#Html.ValidationMessageFor(model => model.ActionId)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
When I open the Create page, I can clearly see that my dropdown for the Action class is fine (correct value -- the Action.Id -- and text -- Action.ActionValue -- ) but when I submit the form, I have the following error: "The parameter conversion from type 'System.String' to type 'MyProject.Models.Action' failed because no type converter can convert between these types."
Help please !!
Right now MVC has no way of connecting your dropdownlist from your view to the ActionId of your ConfigurationFile object.
I would try replacing this line:
#Html.DropDownList("ActionId", String.Empty)
for this
#Html.DropDownListFor(model => model.ActionId, ViewBag.ActionId)
Other than that, I can't think of what else you might have done wrong.
I hope that helps!
This is how I did to circumvent the problem. I just changed my controller this way:
Models.Action act = db.Actions.Find(configurationfile.ActionId);
ModelState.Clear();
configurationfile.Action = act;
TryValidateModel(configurationfile);
And after that, the validation was Ok. A bit hacky (and another possible hit on the DB), but at least, I can keep going.

Resources