I have a Create action that takes an entity object and a HttpPostedFileBase image. The image does not belong to the entity model.
I can save the entity object in the database and the file in disk, but I am not sure how to validate these business rules:
Image is required
Content type must be "image/png"
Must not exceed 1MB
A custom validation attribute is one way to go:
public class ValidateFileAttribute : RequiredAttribute
{
public override bool IsValid(object value)
{
var file = value as HttpPostedFileBase;
if (file == null)
{
return false;
}
if (file.ContentLength > 1 * 1024 * 1024)
{
return false;
}
try
{
using (var img = Image.FromStream(file.InputStream))
{
return img.RawFormat.Equals(ImageFormat.Png);
}
}
catch { }
return false;
}
}
and then apply on your model:
public class MyViewModel
{
[ValidateFile(ErrorMessage = "Please select a PNG image smaller than 1MB")]
public HttpPostedFileBase File { get; set; }
}
The controller might look like this:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel();
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
// The uploaded image corresponds to our business rules => process it
var fileName = Path.GetFileName(model.File.FileName);
var path = Path.Combine(Server.MapPath("~/App_Data"), fileName);
model.File.SaveAs(path);
return Content("Thanks for uploading", "text/plain");
}
}
and the view:
#model MyViewModel
#using (Html.BeginForm("Index", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.LabelFor(x => x.File)
<input type="file" name="#Html.NameFor(x => x.File)" id="#Html.IdFor(x => x.File)" />
#Html.ValidationMessageFor(x => x.File)
<input type="submit" value="upload" />
}
Based on Darin Dimitrov's answer which I have found very helpful, I have an adapted version which allows checks for multiple file types, which is what I was initially looking for.
public override bool IsValid(object value)
{
bool isValid = false;
var file = value as HttpPostedFileBase;
if (file == null || file.ContentLength > 1 * 1024 * 1024)
{
return isValid;
}
if (IsFileTypeValid(file))
{
isValid = true;
}
return isValid;
}
private bool IsFileTypeValid(HttpPostedFileBase file)
{
bool isValid = false;
try
{
using (var img = Image.FromStream(file.InputStream))
{
if (IsOneOfValidFormats(img.RawFormat))
{
isValid = true;
}
}
}
catch
{
//Image is invalid
}
return isValid;
}
private bool IsOneOfValidFormats(ImageFormat rawFormat)
{
List<ImageFormat> formats = getValidFormats();
foreach (ImageFormat format in formats)
{
if(rawFormat.Equals(format))
{
return true;
}
}
return false;
}
private List<ImageFormat> getValidFormats()
{
List<ImageFormat> formats = new List<ImageFormat>();
formats.Add(ImageFormat.Png);
formats.Add(ImageFormat.Jpeg);
formats.Add(ImageFormat.Gif);
//add types here
return formats;
}
}
Here is a way to do it using viewmodel, take a look at whole code here
Asp.Net MVC file validation for size and type
Create a viewmodel as shown below with FileSize and FileTypes
public class ValidateFiles
{
[FileSize(10240)]
[FileTypes("doc,docx,xlsx")]
public HttpPostedFileBase File { get; set; }
}
Create custom attributes
public class FileSizeAttribute : ValidationAttribute
{
private readonly int _maxSize;
public FileSizeAttribute(int maxSize)
{
_maxSize = maxSize;
}
//.....
//.....
}
public class FileTypesAttribute : ValidationAttribute
{
private readonly List<string> _types;
public FileTypesAttribute(string types)
{
_types = types.Split(',').ToList();
}
//....
//...
}
And file length validation in asp.net core:
public async Task<IActionResult> MyAction()
{
var form = await Request.ReadFormAsync();
if (form.Files != null && form.Files.Count == 1)
{
var file = form.Files[0];
if (file.Length > 1 * 1024 * 1024)
{
ModelState.AddModelError(String.Empty, "Maximum file size is 1 Mb.");
}
}
// action code goes here
}
You may want to consider saving the image to database also:
using (MemoryStream mstream = new MemoryStream())
{
if (context.Request.Browser.Browser == "IE")
context.Request.Files[0].InputStream.CopyTo(mstream);
else
context.Request.InputStream.CopyTo(mstream);
if (ValidateIcon(mstream))
{
Icon icon = new Icon() { ImageData = mstream.ToArray(), MimeType = context.Request.ContentType };
this.iconRepository.SaveOrUpdate(icon);
}
}
I use this with NHibernate - entity defined:
public Icon(int id, byte[] imageData, string mimeType)
{
this.Id = id;
this.ImageData = imageData;
this.MimeType = mimeType;
}
public virtual byte[] ImageData { get; set; }
public virtual string MimeType { get; set; }
Then you can return the image as a FileContentResult:
public FileContentResult GetIcon(int? iconId)
{
try
{
if (!iconId.HasValue) return null;
Icon icon = this.iconRepository.Get(iconId.Value);
return File(icon.ImageData, icon.MimeType);
}
catch (Exception ex)
{
Log.ErrorFormat("ImageController: GetIcon Critical Error: {0}", ex);
return null;
}
}
Note that this is using ajax submit. Easier to access the data stream otherwise.
Related
My file upload is not working. I want to upload images.
I have already created the input type file in my view.
Here is my code-
[HttpPost]
public ActionResult AddOrEdit(tbl_employee emp)
{
var getGender = db.tbl_gender.ToList();
SelectList list = new SelectList(getGender, "gender_type", "gender_type", emp);
ViewBag.genderList = list;
if (emp.employee_image != null)
{
string fileName = Path.GetFileNameWithoutExtension(emp.imageFile.FileName);
string extension = Path.GetExtension(emp.imageFile.FileName);
fileName = fileName + extension;
emp.employee_image = "~/Images/Employee/" + fileName;
fileName = Path.Combine(Server.MapPath("~/Images/Employee/"), fileName);
emp.imageFile.SaveAs(fileName);
}
if (emp.employee_id == 0)
{
db.tbl_employee.Add(emp);
db.SaveChanges();
return Json(new { success = true, message = "Saved Successfully" }, JsonRequestBehavior.AllowGet);
}
else
{
db.Entry(emp).State = EntityState.Modified;
db.SaveChanges();
return Json(new { success = true, message = "Updated Successfully" }, JsonRequestBehavior.AllowGet);
}
}
Here is my tbl_employee.cs
public long employee_id { get; set; }
public string employee_image { get; set; }
public HttpPostedFileBase imageFile { get; set; }
What is wrong here?
Include HttpPostedFileBase in your function for getting the uploaded file in your AddOrEdit function.
public ActionResult AddOrEdit(tbl_employee emp, HttpPostedFileBase Image)
{
}
For more explanation, include also the view with your question.
Ensure your view contain code for image upload.
#using (Html.BeginForm("AddOrEdit", "Employee", null, FormMethod.Post, new { enctype = "multipart/form-data" }))
Can anyone help i'm using MVC 5 and doing a code on uploading and displaying photos and i'm having an error (The name ' file ' does not exist in the current content) please help
This is the controller i'm using:
public class UploadController : Controller
{
private DataContext db = new DataContext();
public ActionResult Index()
{
var model = new UploadViewModel();
return View(model);
}
[HttpPost]
public ActionResult Upload(UploadViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
UploadDbModel fileUploadModel = new UploadDbModel();
try
{
if (file.ContentLength > 0)
{
byte[] uploadFile = new byte[model.File.InputStream.Length];
model.File.InputStream.Read(uploadFile, 0, uploadFile.Length);
fileUploadModel.FileName = model.File.FileName;
fileUploadModel.File = uploadFile;
db.UploadDbModels.Add(fileUploadModel);
db.SaveChanges();
}
return Content("FILE SUCCESSFULLY UPLOADED");
}
catch
{
return Content("UPLOAD FAILED");
}
}
public ActionResult Download()
{
return View(db.UploadDbModels.ToList());
}
public FileContentResult FileDownload(int? id)
{
byte[] fileData;
string fileName;
UploadDbModel fileRecord = db.UploadDbModels.Find(id);
fileData = (byte[])fileRecord.File.ToArray();
fileName = fileRecord.FileName;
return File(fileData, "text", fileName);
}
}
In Asp.Net MVC we have to use HttpPostedFileBase for Uploaded files as shown below :-
[HttpPost]
public ActionResult Upload(UploadViewModel model, HttpPostedFileBase file)
{
if (file != null)
{
int byteCount = file.ContentLength; <---Your file Size or Length
.............
.............
}
}
Try and use system.web, just add the reference on the assembly hope it works.
I've seen a lot of similar posts on this, but haven't found the answer specific to controller parameters.
I've written a custom attribute called AliasAttribute that allows me to define aliases for parameters during model binding. So for example if I have: public JsonResult EmailCheck(string email) on the server and I want the email parameter to be bound to fields named PrimaryEmail or SomeCrazyEmail I can "map" this using the aliasattribute like this: public JsonResult EmailCheck([Alias(Suffix = "Email")]string email).
The problem: In my custom model binder I can't get a hold of the AliasAttribute class applied to the email parameter. It always returns null.
I've seen what the DefaultModelBinder class is doing to get the BindAttribute in reflector and its the same but doesn't work for me.
Question: How do I get this attribute during binding?
AliasModelBinder:
public class AliasModelBinder : DefaultModelBinder
{
public static ICustomTypeDescriptor GetTypeDescriptor(Type type)
{
return new AssociatedMetadataTypeTypeDescriptionProvider(type).GetTypeDescriptor(type);
}
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = base.BindModel(controllerContext, bindingContext);
var descriptor = GetTypeDescriptor(bindingContext.ModelType);
/*************************/
// this next statement returns null!
/*************************/
AliasAttribute attr = (AliasAttribute)descriptor.GetAttributes()[typeof(AliasAttribute)];
if (attr == null)
return null;
HttpRequestBase request = controllerContext.HttpContext.Request;
foreach (var key in request.Form.AllKeys)
{
if (string.IsNullOrEmpty(attr.Prefix) == false)
{
if (key.StartsWith(attr.Prefix, StringComparison.InvariantCultureIgnoreCase))
{
if (string.IsNullOrEmpty(attr.Suffix) == false)
{
if (key.EndsWith(attr.Suffix, StringComparison.InvariantCultureIgnoreCase))
{
return request.Form.Get(key);
}
}
return request.Form.Get(key);
}
}
else if (string.IsNullOrEmpty(attr.Suffix) == false)
{
if (key.EndsWith(attr.Suffix, StringComparison.InvariantCultureIgnoreCase))
{
return request.Form.Get(key);
}
}
if (attr.HasIncludes)
{
foreach (var include in attr.InlcludeSplit)
{
if (key.Equals(include, StringComparison.InvariantCultureIgnoreCase))
{
return request.Form.Get(include);
}
}
}
}
return null;
}
}
AliasAttribute:
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class AliasAttribute : Attribute
{
private string _include;
private string[] _inlcludeSplit = new string[0];
public string Prefix { get; set; }
public string Suffix { get; set; }
public string Include
{
get
{
return _include;
}
set
{
_include = value;
_inlcludeSplit = SplitString(_include);
}
}
public string[] InlcludeSplit
{
get
{
return _inlcludeSplit;
}
}
public bool HasIncludes { get { return InlcludeSplit.Length > 0; } }
internal static string[] SplitString(string original)
{
if (string.IsNullOrEmpty(original))
{
return new string[0];
}
return (from piece in original.Split(new char[] { ',' })
let trimmed = piece.Trim()
where !string.IsNullOrEmpty(trimmed)
select trimmed).ToArray<string>();
}
}
Usage:
public JsonResult EmailCheck([ModelBinder(typeof(AliasModelBinder)), Alias(Suffix = "Email")]string email)
{
// email will be assigned to any field suffixed with "Email". e.g. PrimaryEmail, SecondaryEmail and so on
}
Gave up on this and then stumbled across the Action Parameter Alias code base that will probably allow me to do this. It's not as flexible as what I started out to write but probably can be modified to allow wild cards.
what I did was make my attribute subclass System.Web.Mvc.CustomModelBinderAttribute which then allows you to return a version of your custom model binder modified with the aliases.
example:
public class AliasAttribute : System.Web.Mvc.CustomModelBinderAttribute
{
public AliasAttribute()
{
}
public AliasAttribute( string alias )
{
Alias = alias;
}
public string Alias { get; set; }
public override IModelBinder GetBinder()
{
var binder = new AliasModelBinder();
if ( !string.IsNullOrEmpty( Alias ) )
binder.Alias = Alias;
return binder;
}
}
which then allows this usage:
public ActionResult Edit( [Alias( "somethingElse" )] string email )
{
// ...
}
These links didn't help me:
Way 1
Way 2
Example:
//Model:
public class Group
{
public int Id { get; set; }
public File File { get; set; }
}
//Controller:
[HttpPost]
public void SaveGroup([FromBody]Group group) {}
//Formatter:
public class MultipartFormFormatter : MediaTypeFormatter
{
private const string StringMultipartMediaType = "multipart/form-data";
public MultipartFormFormatter()
{
this.SupportedMediaTypes.Add(new MediaTypeHeaderValue(StringMultipartMediaType));
}
public override bool CanReadType(Type type)
{
return true;
}
public override bool CanWriteType(Type type)
{
return false;
}
public async override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
{
//Implementation? What here should be?
}
}
What should the method ReadFromStreamAsync return?
How do I make it so that you can properly transmit parameter to the action?
public class MultipartFormFormatter : FormUrlEncodedMediaTypeFormatter
{
private const string StringMultipartMediaType = "multipart/form-data";
private const string StringApplicationMediaType = "application/octet-stream";
public MultipartFormFormatter()
{
this.SupportedMediaTypes.Add(new MediaTypeHeaderValue(StringMultipartMediaType));
this.SupportedMediaTypes.Add(new MediaTypeHeaderValue(StringApplicationMediaType));
}
public override bool CanReadType(Type type)
{
return true;
}
public override bool CanWriteType(Type type)
{
return false;
}
public override async Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
{
var parts = await content.ReadAsMultipartAsync();
var obj = Activator.CreateInstance(type);
var propertiesFromObj = obj.GetType().GetRuntimeProperties().ToList();
foreach (var property in propertiesFromObj.Where(x => x.PropertyType == typeof(FileModel)))
{
var file = parts.Contents.FirstOrDefault(x => x.Headers.ContentDisposition.Name.Contains(property.Name));
if (file == null || file.Headers.ContentLength <= 0) continue;
try
{
var fileModel = new FileModel(file.Headers.ContentDisposition.FileName, Convert.ToInt32(file.Headers.ContentLength), ReadFully(file.ReadAsStreamAsync().Result));
property.SetValue(obj, fileModel);
}
catch (Exception e)
{
}
}
foreach (var property in propertiesFromObj.Where(x => x.PropertyType != typeof(FileModel)))
{
var formData = parts.Contents.FirstOrDefault(x => x.Headers.ContentDisposition.Name.Contains(property.Name));
if (formData == null) continue;
try
{
var strValue = formData.ReadAsStringAsync().Result;
var valueType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
var value = Convert.ChangeType(strValue, valueType);
property.SetValue(obj, value);
}
catch (Exception e)
{
}
}
return obj;
}
private byte[] ReadFully(Stream input)
{
var buffer = new byte[16 * 1024];
using (var ms = new MemoryStream())
{
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
}
return ms.ToArray();
}
}
}
public class FileModel
{
public FileModel(string filename, int contentLength, byte[] content)
{
Filename = filename;
ContentLength = contentLength;
Content = content;
}
public string Filename { get; set; }
public int ContentLength { get; set; }
public byte[] Content { get; set; }
}
Please see below link for detail implementation:
https://github.com/iLexDev/ASP.NET-WebApi-MultipartDataMediaFormatter
Nuget:
https://www.nuget.org/packages/MultipartDataMediaFormatter/
I actually need to do "multipart/form-data" file upload and model binding today, I tried above lib from nuget and turns out it works as my expectation. Validation on model also works fine. Hopefully it helps to answer your question.
Is it exists kind of plugin like Paperclip for Rails?
it is really painful to implement own system for uploading files the resize it...
will be cool to have Attribute for model that will get params like so:
Model:
[Paperclip(Sizes={thumb="100x20",big="200x40"},Path="~/public/")]
public string Image{get;set;}
View:
Html.Editor(x=>x.Image)
here is small tutorial for rails.
Looks like this question is pretty old, I've stumbled across it by accident and because it doesn't seem to be answered yet, I decided to bring my 2ยข.
I don't know if such plugin exists for ASP.NET but it would be trivially easy to write one:
[AttributeUsage(AttributeTargets.Property)]
public class PaperClipAttribute : Attribute, IMetadataAware
{
public PaperClipAttribute(string uploadPath, params string[] sizes)
{
if (string.IsNullOrEmpty(uploadPath))
{
throw new ArgumentException("Please specify an upload path");
}
UploadPath = uploadPath;
Sizes = sizes;
}
public string UploadPath { get; private set; }
public string[] Sizes { get; private set; }
public void OnMetadataCreated(ModelMetadata metadata)
{
metadata.AdditionalValues["PaperClip"] = this;
}
}
then define a custom model binder for the HttpPostedFileBase type:
public class PaperClipModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var paperClip = bindingContext.ModelMetadata.AdditionalValues["PaperClip"] as PaperClipAttribute;
if (paperClip == null)
{
return base.BindModel(controllerContext, bindingContext);
}
var uploadedFile = base.BindModel(controllerContext, bindingContext) as HttpPostedFileBase;
if (uploadedFile == null)
{
return null;
}
var uploadPath = controllerContext.HttpContext.Server.MapPath(paperClip.UploadPath);
if (!Directory.Exists(uploadPath))
{
throw new ArgumentException(string.Format("The specified folder \"{0}\" does not exist", uploadPath));
}
var sizes =
(from size in paperClip.Sizes
let tokens = size.Split('x')
select new Size(int.Parse(tokens[0]), int.Parse(tokens[1]))
).ToArray();
foreach (var size in sizes)
{
var extension = Path.GetExtension(uploadedFile.FileName);
var outputFilename = Path.Combine(
uploadPath,
Path.ChangeExtension(
string.Format("image{0}x{1}", size.Width, size.Height),
extension
)
);
Resize(uploadedFile.InputStream, outputFilename, size);
}
return base.BindModel(controllerContext, bindingContext);
}
private void Resize(Stream input, string outputFile, Size size)
{
using (var image = Image.FromStream(input))
using (var bmp = new Bitmap(size.Width, size.Height))
using (var gr = Graphics.FromImage(bmp))
{
gr.CompositingQuality = CompositingQuality.HighSpeed;
gr.SmoothingMode = SmoothingMode.HighSpeed;
gr.InterpolationMode = InterpolationMode.HighQualityBicubic;
gr.DrawImage(image, new Rectangle(0, 0, size.Width, size.Height));
bmp.Save(outputFile);
}
}
}
which will be registered in Application_Start:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
ModelBinders.Binders[typeof(HttpPostedFileBase)] = new PaperClipModelBinder();
}
and we are pretty much done. All that's left is classic stuff.
View model:
public class MyViewModel
{
[PaperClip("~/App_Data", "100x20", "200x40", "640x480")]
[Required(ErrorMessage = "Please select a file to upload")]
public HttpPostedFileBase Image { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new MyViewModel());
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
return Content("thanks for uploading");
}
}
View:
#model MyViewModel
#using (Html.BeginForm(null, null, FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.LabelFor(x => x.Image)
<input type="file" name="image" />
#Html.ValidationMessageFor(x => x.Image)
<input type="submit" value="Upload" />
}