tags:

views:

56

answers:

1

I have a model with ImagePath and PDFPath properties.

When they click save, that calls the POST action method in the controller. I need to check that they have only uploaded image files and pdf files. If they have uploaded something other than these filetypes I want to set a ModelState error with the following:

ModelState.AddModelError("ImagePath", "Only image files are accepted");
ModelState.SetModelValue("ImagePath", new ValueProviderResult(null, null, null));

The problem is I need to set the correct properties in the AddModelError. The problem being they could have put a *.doc file in the ImagePath or PDFPath so I don't know which one to report as the error field.

How do I also make sure that they do only upload ceratin filetypes? RegEx?

Thanks

EDIT: Here is my controller code.

[AcceptVerbs(HttpVerbs.Post)]
    [Authorize]
    public ActionResult Create([Bind(Prefix = "", Exclude = "ID")] News item)
    {
        string imageUrl = "";
        string pdfurl = "";

        try
        {
            News.CheckForErrors(item);
        }
        catch (RulesException ex)
        {
            ex.AddModelStateErrors(ModelState, null);
        }

        foreach (string inputTagName in Request.Files)
        {
            HttpPostedFileBase file = Request.Files[inputTagName];
            if (file.ContentLength > 0)
            {
                string filePath = Path.Combine(HttpContext.Server.MapPath("/uploads"), Path.GetFileName(file.FileName));
                if (Path.GetExtension(file.FileName).ToLower() != ".jpg" || Path.GetExtension(file.FileName).ToLower() != ".pdf")
                {
                    //HELP! - Which model has the property error ImagePath/PDFPath?
                    ModelState.AddModelError("ImagePath", "Only JPG image files are accepted");
                    ModelState.SetModelValue("ImagePath", new ValueProviderResult(null, null, null));
                    break;
                }

                imageUrl = "/uploads/" + file.FileName;
            }
        }

        if (!ModelState.IsValid)
        {

                return View(item);

        }

        try
        {
            item.Save(User.Identity.Name);

        }
        catch (Exception x)
        {

        }



        return RedirectToAction("Index");

    }
+1  A: 

You need something like GetFileByExtension() method:

HttpPostedFileBase GetFileByExtension(HttpFileCollectionBase files, string[] extensions)
{
    Func<string, bool> extensionIsValid =
        fileExtension => extensions.Any(
            extension => String.Compare(extension, fileExtension, true) == 0
        );

    return files.Cast<string>().Select(x => files[x]).Single(
        file => extensionIsValid(System.IO.Path.GetExtension(file.FileName))
        );
}

or:

IList<HttpPostedFileBase> GetAllFilesByExtension(HttpFileCollectionBase files, string[] extensions)
{
    Func<string, bool> extensionIsValid =
        fileExtension => extensions.Any(
            extension => String.Compare(extension, fileExtension, true) == 0
        );

    return files.Cast<string>().Select(x => files[x]).Where(
        file => extensionIsValid(System.IO.Path.GetExtension(file.FileName))
        ).ToList();
}

example:

var imageFile = GetFileByExtension(Request.Files,
                                   new [] { ".jpg", ".gif", ".png", ".bmp" });

var pdfFile = GetFileByExtension(Request.Files, new [] { ".pdf" });

or:

var imageFiles = GetAllFilesByExtension(Request.Files,
                                   new [] { ".jpg", ".gif", ".png", ".bmp" });

var pdfFiles = GetAllFilesByExtension(Request.Files, new [] { ".pdf" });

UPDATED:

Maybe you should try uploading using separate forms/actions for different uploads (something like /upload/create?newsid=1?uploadtype=image for images and /upload/create?newsid=1?uploadtype=pdf for PDFs). Your Upload.Create action could look like this:

public class UploadController : Controller
{
    public ActionResult Create(int newsId, string uploadType)
    {
        if(uploadType == "image")
        {
            // uploading only images
            var imageFiles = GetAllFilesByExtension(Request.Files, new [] { ".jpg", ".gif", ".png", ".bmp" });
            ...
        }
    }
}

In this case you always know the type of files, which should be uploaded to the action

eu-ge-ne
I've just realised this has not really answered my question. If they upload an image of a different extension which means its not valid image or valid pdf then how do I set the ModelState Error onto the correct property?
Jon
Something like this?:var imageFiles = GetAllFilesByExtension(Request.Files, new [] { ".jpg", ".gif", ".png", ".bmp" });if(imageFiles.Count() == 0) ModelState.AddModelError("ImagePath", "Only JPG image files are accepted");
eu-ge-ne
What if they put a pdf file in the image input?
Jon
I've updated my answer. Maybe this helps you?
eu-ge-ne
The update is not a workable solution I have a News item with properties relating to columns in a database. I use the defaultmodelbinder to bind it then I call Save in my action.
Jon
But you could read News item from database by Id, update it, and then save?
eu-ge-ne
This is a create action though so nothing to update
Jon
In that case maybe it is better to have a simple list of uploads and one validation field in your create form? And decide on the sever side which uploaded files should go to images or pdfs?
eu-ge-ne
validFiles = files.Cast<HttpPostedFileBase>().Where( file => extensionIsValid(System.IO.Path.GetExtension(file.FileName)) ).ToList(); fails to cast string to HttpPostedFileBase
Jon
What type "files" variable is? It should be of HttpFileCollectionBase type.
eu-ge-ne
It is but in your demo you cast it to HttpPostedFileBase
Jon
Yes, exactly. Cast<T>() method convert IEnumerable to IEnumerable<T>. In my example it converts HttpFileCollectionBase to IEnumerable<HttpPostedFileBase>. I've tested my example - it works.
eu-ge-ne
The cast is what is failing. I've stripped all the code back to just the cast and that's what fails
Jon
Could you post your code where exactly is it failing?
eu-ge-ne
validFiles = files.Cast<HttpPostedFileBase>().Where( file => extensionIsValid(System.IO.Path.GetExtension(file.FileName)) ).ToList();validFiles is of type IList<HttpPostedFileBase>
Jon
You need Cast to HttpPostedFileBase ONLY if your files var is HttpFileCollectionBase (because implements only IEnumerable). I'm using var files = Request.Files - I need Cast<HttpPostedFileBase>() method. If your files var is IList<HttpPostedFileBase> you don't need Cast method beacuse IList<HttpPostedFileBase> implements IEnumerable<HttpPostedFileBase>. Hope this helps
eu-ge-ne
files is HttpFileCollectionBase as in your example. validFiles is IList<HttpPostedFileBase>
Jon
My apologies. I've found the bug :( - I've updated my answer - now it works with Cast<string>().Select(x => files[x]) instead of Cast<HttpPostedFileBase>(). I have a similar project but it uses a collection of HttpPostedFileBase as action parameters with custom binder but now I see that my case and yours are completely different.
eu-ge-ne