MVC File upload validation - asp.net-mvc

I have a simple situation where I have a page that uploads Files, for some importing. At the moment, all I have is a file upload input on my page.
this is what my get controller looks like
public ActionResult FileUpload()
{
return View();
}
This is what my view looks like
#{
ViewBag.Title = "FileUpload";
}
<h2>FileUpload</h2>
<form action="/Home/FileUpload" method="post" enctype="multipart/form-data">
<input type="file" id="newFile" name="newFile" />
<input type="submit" id="submitButton" value="Submit" />
</form>
and this is what my post action looks like
[HttpPost]
public ActionResult FileUpload(HttpPostedFileBase newFile)
{
if (newFile.ContentLength > 0)
{
//do stuff here
}
return View("Index");
}
You will of course notice there is no mention of a model here as I cannot find a way to create a model for this situation. I would like to have some very basic validation, along the lines of 'please choose a file before you upload', thats all.
Is there a way to achieve this?!
Thanks in advance
Will

Create model class with string property newFile and put a Required on it.
In controller accept not HttpPostedFile but your model class.

You should add the client side validation manually:
<input type="file" data-val="true" data-val-required="please select a file" name="file" />
#Html.ValidationMessage("file")

Related

Fields marked HiddenFor not binding to Model in MVC4

I have implemented an image upload child action form for an application. I have a strongly typed partial view.
public class ImageViewModel{
public long ImageId{get;set;}
public long OwnerId{get;set;}
public string ImageName{get;set;}
public string ImageDescription{get;set;}
public IEnumerable<HttpPostedFileBase> Files { get; set; }
}
Razor code looks something like this:
<form action="UploadImage" method="post" enctype="multipart/form-data">
#Html.ValidationSummary()
#Html.HiddenFor(m => m.OwnerId)
#Html.HiddenFor(m => m.ImageId)
#HtmlEditorFor(m=>m.ImageName)
<input type="file" name="Files" id="file0" />
<input type="submit" value="Upload" />
</form>
Here is the problem. When form is posted back, the model has uploaded file and ImageName value in it. But values that were bound using HiddenFor are missing.
[HttpPost]
public ActionResult UploadImage(ImageViewModel model)
{ ...}
I have checked HTML source. Hidden fields are rendered corrected with Id and names matching to property named of model. On post back I checked the raw request. Both hidden fields are carried in Form collection. But model binding is not setting the values of these fields in properties.
Is there something that I am missing about these hidden fields?
Thanks

ASP .NET MVC 2 - Browse and add link to document

I am developing an internal project managment dashboard using MVC 2. One of the requirements is to link to already existing documents on one of our local servers, in other words, browse to server, select file, click add and the view for that project will contain the link. Here is what I have (I've left some details out for brevity):
Model:
public class AddDocumentModel
{
public HttpPostedFileBase DocumentLink { get; set; }
}
View:
<%
using (Html.BeginForm(MVC.ProjectDetails.Actions.AddDoc(this.Model.ProjectID),
FormMethod.Post, new { enctype = "multipart/form-data" }))
{%>
<%=Html.TextBoxFor(a => a.DocumentLink,
new { type = "file", style = "width:100%;"})%>
<input type="submit" value="Add Document Link" />
<%} %>
Controller:
[AcceptVerbs(HttpVerbs.Post)]
public virtual ActionResult AddDoc(AddDocumentModel docModel)
{
var model = _projectManagementService.AddDocumentLink(
docModel.DocumentLink.FileName);
}
So, as you can see I am using an html textbox for file upload but not actually uploading, just attempting to grab path and filename and use that as link. However due to security constraints this will only work in IE, as no other browser will let you get at the path. Also, if the user uses a mapped drive it won't work as the full path will not be used, so they have to navaigate directly to the server.
Can anyone think of another way to do this? I would like to be able to use the browse functionality offered by upload functionality, but not be tied by constraints.
At the moment the only (low tech) solution I can think of is for the user to explicitly paste the link into a text box. But would prefer something a lot more friendly.
Thanks in advance. First posted question too, so be kind :-)
If I were you I would allow them to either upload a new file or paste in the location of an existing file. There's no reason to try to reuse a file upload element to do what you're doing.
Form example (didn't feel like writing out the <%=Html %>
<form>
<div>
<input type="radio" name="AddDocumentType" value="New" />
<label for="NewDocument">Upload New Document</label>
<input type="file" id="NewDocument" name="NewDocument" />
</div>
<div>
<input type="radio" name="AddDocumentType" value="Link" />
<label for="LinkDocument">Link To Existing Document</label>
<input type="text" id="LinkDocument" name="LinkDocument" />
</div>
<input type="submit" value="Add Document Link" />
</form>
Model
public enum AddDocumentType
{
New,
Link
}
public class AddDocumentModel
{
public AddDocumentType AddDocumentType { get; set; }
public HttpPostedFileBase NewDocument { get; set; }
public string LinkDocument { get; set; }
}

How to unencode attribute when calling Html.BeginForm

I'm trying to hook into Google Analytics and have this:
#using (Html.BeginForm("Register", "Account", FormMethod.Post,
new { id = "account-register-form",
onsubmit = "_gaq.push(['_link', 'someurl'])" }))
My rendered view then looks like this:
<form action="/Account/Register/Basic" id="account-register-form" method="post"
onsubmit="_gaq.push(['_link', 'someurl'])">
I have tried Html.Raw("_gaq.push(['_link', 'someurl'])") but this does not work, because I think BeginForm does the encoding.
Code works fine if don't turn off encoding but you can turn off attribute encoding by creating a class like this:
public class HtmlAttributeEncodingNot : System.Web.Util.HttpEncoder
{
protected override void HtmlAttributeEncode(string value, System.IO.TextWriter output)
{
output.Write(value);
}
}
and adding this to web.config under :
<httpRuntime encoderType="HtmlAttributeEncodingNot"/>
You don't need to unencode anything. What you have is perfectly valid markup and working javascript, as seen in the following live demo:
<form action="#" method="get" onsubmit="alert('some test');">
<input type="submit" value="OK" />
</form>
So keep your code as is.
If you don't have to use the Html.BeginForm then use can use the code snippet below to solve your formatting issue.
<form action="#Url.Action("Register", "Account")"
method="POST" id="account-register-form"
onsubmit="_gaq.push(['_link', 'someurl'])">
</form>
This outputs the html you require.

asp.net mvc default model binding problem

I have some problems with ASP.NET MVC’s default model binder. The View contains HTML like this:
<input name="SubDTO[0].Id" value="1" type="checkbox">
<input name="SubDTO[1].Id" value="2" type="checkbox">
This is my simplified ‘model’:
public class SubDTO
{
public virtual string Id { get; set; }
}
public class DTO
{
public List<SubDTO> SubDTOs { get; set; }
public DTO()
{
SubDTOs = new List< SubDTO>();
}
}
All this works fine if the user selects at least the first checkbox (SubDTO[0].Id). The controller ‘receives’ a nicely initialised/bound DTO. However, if the first check box is not selected but only, for example, SubDTO[1].Id the object SubDTOs is null. Can someone please explain this ‘strange’ behaviour and how to overcome it? Thanks.
Best wishes,
Christian
PS:
The controller looks like this:
[Transaction]
[AcceptVerbs(HttpVerbs.Post)]
public RedirectToRouteResult Create(DTO DTO)
{
...
}
PPS:
My problem is that if I select checkbox SubDTO[0].Id, SubDTO[1].Id, SubDTO[2].Id SubDTOs is initialised. But if I just select checkbox SubDTO[1].Id, SubDTO[2].Id (NOT the first one!!!) SubDTOs remains null. I inspected the posted values (using firebug) and they are posted!!! This must be a bug in the default model binder or might be missing something.
This behavior is "by design" in html. If a check-box is checked its value is sent to the server, if it is not checked nothing is sent. That's why you get null in your action and you'll not find value in the posted form either. The way to workaround this is to add a hidden field with the same name and some value AFTER the check-box like this:
<input name="SubDTO[0].Id" value="true" type="checkbox">
<input name="SubDTO[0].Id" value="false" type="hidden">
<input name="SubDTO[1].Id" value="true" type="checkbox">
<input name="SubDTO[1].Id" value="false" type="hidden">
In this way if you check the check-box both values will be sent but the model binder will take only the first. If the check-box is not checked only the hidden field value will be sent and you\ll get it in the action instead of null.
I think this post on Scott Hanselman's blog will explain why. The relevant line is:
The index must be zero-based and unbroken. In the above example, because there was no people[2], we stop after Abraham Lincoln and don’t continue to Thomas Jefferson.
So, in your case because the first element is not returned (as explained by others as the default behaviour for checkboxes) the entire collection is not being initialized.
Change the markup as follows:
<input name="SubDTOs" value="<%= SubDTO[0].Id %>" type="checkbox">
<input name="SubDTOs" value="<%= SubDTO[1].Id %>" type="checkbox">
What's being returned by your original markup is an unrelated set of parameters, i.e. like calling RedirectToRouteResult Create(SubDTO[0].id, SubDTO[1].id, ..., SubDTO[n].id) which is clearly not what you want, you want an array returned into your DTO object so by giving all the checkboxes the same name the return value to your function will be an array of ids.
EDIT
Try this:
<input name="SubDTO[0].Id" value="<%= SubDTO[0].Id %>" type="checkbox">
<input name="SubDTO[0].Id" value="false" type="hidden">
<input name="SubDTO[1].Id" value="<%= SubDTO[1].Id %>" type="checkbox">
<input name="SubDTO[1].Id" value="false" type="hidden">
You have to return something to make sure there is an element for each index, I suspect that any gap will cause a problem so I'd suggest using a 'null' ID, for example 0 or -1 and then process that out later in your code. Another answer would be a custom model binder.
There is always the alternate option of adding a property to your class that takes an array of strings and creates the SubDTO array from that.
public List<string> SubDTOIds
{
get { return SubDTO.Select(s=>s.Id).ToList(); }
set
{
SubDTOs = new List< SubDTO>();
foreach (string id in value)
{
SubDTOs.Add(new SubDTO { Id = id });
}
}
}
or something like that

Handling 2 buttons submit Actions in a single View/Form - ASP.NET MVC 2 RTM

I have a View in which the user is able to upload a file to the server.
In this view I also have 2 buttons: one to Upload a file and other to Download the last file imported.
In my Controller I created 2 action methods: Import and Export.
How could I manage to redirect each button click to the proper action method in my Controller?
I have tried Html.ActionLink:
<%= Html.ActionLink("Upload", "Import", "OracleFile")%>
<%= Html.ActionLink("Download", "Export", "OracleFile")%>
Html.ActionLink didn't do the trick. The action links were taking me to the right Action methods but they were generating a GET request. This way Request.Files.Count = 0.
I need a POST request.
Note: the most intriguing part is that the upload was working and all of sudden it stopped working. I've seen that some people are having the same problem with FileUpload tasks in which the Request.Files is always Empty. I think it's empty because you need a post to the server. Isn't it?
maybe this will give u the idea:
view:
<form enctype="multipart/form-data" method="post" action="/Media/Upload/Photo">
<input type="file" name="file" id="file" />
<input type="submit" name= "submitImport" value="Upload" />
<input type="submit" name = "submitExport" value="Download" />
</form>
controller:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Action (FormCollection formCollection)
{
if (formCollection["submitImport"] != null)
{
return Import(formCollection);
}
if (formCollection["submitExport"] != null)
{
return Export(formCollection);
}
}
the Export and Import are the appropriateactions
You have to use a "multipart/form-data" form, and submit the form. No ActionLink.
<form enctype="multipart/form-data" method="post" action="/Media/Upload/Photo">
<input type="file" name="file" id="file" />
<input type="submit" value="Upload" />
</form>
To generate a POST request for the upload, use the File Input form element and just post back to the server ala normal.
http://www.w3schools.com/jsref/dom_obj_fileupload.asp
Have a look at this blog post from Scott Hanselman.
http://www.hanselman.com/blog/ABackToBasicsCaseStudyImplementingHTTPFileUploadWithASPNETMVCIncludingTestsAndMocks.aspx

Resources