Asp.net mvc 在MVC中检查图像mime、大小等

Asp.net mvc 在MVC中检查图像mime、大小等,asp.net-mvc,entity-framework,asp.net-mvc-5,Asp.net Mvc,Entity Framework,Asp.net Mvc 5,我在这里发现了一种非常好的方法,可以检查用户上传的文件是否是图片,以及我在尝试实现它时遇到的问题 这是我找到的用来检查文件的类 public static class HttpPostedFileBaseExtensions { public const int ImageMinimumBytes = 512; public static bool IsImage(this HttpPostedFileBase postedFile) {

我在这里发现了一种非常好的方法,可以检查用户上传的文件是否是图片,以及我在尝试实现它时遇到的问题

这是我找到的用来检查文件的类

public static class HttpPostedFileBaseExtensions
{
    public const int ImageMinimumBytes = 512;

    public static bool IsImage(this HttpPostedFileBase postedFile)
    {            
        //-------------------------------------------
        //  Check the image mime types
        //-------------------------------------------
        if (postedFile.ContentType.ToLower() != "image/jpg" &&
                    postedFile.ContentType.ToLower() != "image/jpeg" &&
                    postedFile.ContentType.ToLower() != "image/pjpeg" &&
                    postedFile.ContentType.ToLower() != "image/gif" &&
                    postedFile.ContentType.ToLower() != "image/x-png" &&
                    postedFile.ContentType.ToLower() != "image/png")
        {
            return false;
        }

        //-------------------------------------------
        //  Check the image extension
        //-------------------------------------------
        if (Path.GetExtension(postedFile.FileName).ToLower() != ".jpg"
            && Path.GetExtension(postedFile.FileName).ToLower() != ".png"
            && Path.GetExtension(postedFile.FileName).ToLower() != ".gif"
            && Path.GetExtension(postedFile.FileName).ToLower() != ".jpeg")
        {
            return false;
        }

        //-------------------------------------------
        //  Attempt to read the file and check the first bytes
        //-------------------------------------------
        try
        {
            if (!postedFile.InputStream.CanRead)
            {
                return false;
            }

            if (postedFile.ContentLength < ImageMinimumBytes)
            {
                return false;
            }

            byte[] buffer = new byte[512];
            postedFile.InputStream.Read(buffer, 0, 512);
            string content = System.Text.Encoding.UTF8.GetString(buffer);
            if (Regex.IsMatch(content, @"<script|<html|<head|<title|<body|<pre|<table|<a\s+href|<img|<plaintext|<cross\-domain\-policy",
                RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Multiline))
            {
                return false;
            }
        }
        catch (Exception)
        {
            return false;
        }

        //-------------------------------------------
        //  Try to instantiate new Bitmap, if .NET will throw exception
        //  we can assume that it's not a valid image
        //-------------------------------------------

        try
        {
            using (var bitmap = new System.Drawing.Bitmap(postedFile.InputStream))
            {
            }
        }
        catch (Exception)
        {
            return false;
        }

        return true;
    }
}    
我的ProfileController已更改。我添加了
HttpPostedFileBase
作为参数,并使用了这一行,
if(HttpPostedFileBaseExtensions.IsImage(file)==true)
,我认为这会解决问题,但没有任何区别

[HttpPost]
    public ActionResult Edit(Profile profile, HttpPostedFileBase file)
    {
        if (HttpPostedFileBaseExtensions.IsImage(file) == true)
            {
                if (ModelState.IsValid)
                    {               

                        repository.SaveProfile(profile);
                        TempData["message"] = string.Format("{0} has been saved", profile.Name);
                        return RedirectToAction("List");
                    }
                 else
                    {
                        // there is something wrong with the data values
                        return View(profile);
                    }
            }
        else
        {
            return View(ViewBag);
        }            
    }
最后是存储库中的
SaveProfile
方法

public void SaveProfile(Profile profile)
    {
        Profile dbEntry = context.Profiles.Find(profile.ProfileID);                        
        if (profile.ProfileID == 0)
        {
            context.Profiles.Add(profile);
        }
        else
        {
            if (dbEntry != null)
            {                    
                    dbEntry.Name = profile.Name;
                    dbEntry.Rate = profile.Rate;
                    dbEntry.Intro = profile.Intro;
                    dbEntry.Description = profile.Description;
                if (profile.ImageData != null)
                {

                    dbEntry.ImageData = profile.ImageData;
                    dbEntry.ImageMimeType = profile.ImageMimeType;
                }                                                               
            }
        }
        context.SaveChanges();
    }   

我还尝试编辑
SaveProfile
方法,但无法实现类中的所有函数,而是希望将其单独使用。你知道我哪里出错了吗?

你有很多问题,有大的,也有小的

首先也是最重要的,您使用的扩展方法是错误的。添加扩展的全部意义在于它成为该类型实例的一个方法。
this
关键字param是隐式的,由调用方法的对象的反向引用填充,而不是显式传递。换言之,你应该为你的有条件的

if (file.IsImage())
{
    ...
还要注意的是,与
true
没有可比性。虽然这没什么错,但完全没有必要,您已经有了一个布尔值

第二,虽然将此条件放在代码的其余部分可以有效地防止对象被保存,但它不会为用户提供指导。相反,您应该执行以下操作:

if (!file.IsImage())
{
    ModelState.AddModelError("file", "Upload must be an image");
}

if (ModelState.IsValid)
{
    ...
var binaryReader = new BinaryReader(file.InputStream);
dbEntry.ImageData = binaryReader.ReadBytes(file.InputStream.Length);
dbEntry.ImageMimeType = file.ContentType;
通过将错误添加到
ModelState
,不仅会导致
IsValid
为false,而且在再次返回表单时,会向用户显示实际的错误消息

第三,通过尝试从数据库中选择现有概要文件实例,您将获得该实例或null。因此,您不需要检查
ProfileId
是否为0,这是一个非常脆弱的检查(用户只需将隐藏字段的值更改为其他值即可修改现有项)。相反,只要做:

    var dbEntry = context.Profiles.Find(profile.ProfileID);                        
    if (dbEntry == null)
    {
        // add profile
    }
    else
    {
        // update profile
    }
第五,你永远不会用
文件做任何事情。在某个时候,你应该做如下事情:

if (!file.IsImage())
{
    ModelState.AddModelError("file", "Upload must be an image");
}

if (ModelState.IsValid)
{
    ...
var binaryReader = new BinaryReader(file.InputStream);
dbEntry.ImageData = binaryReader.ReadBytes(file.InputStream.Length);
dbEntry.ImageMimeType = file.ContentType;
最后,这比任何东西都更具风格,但过度使用不必要的
else
块会使代码更难阅读。您可以简单地让错误案例失效。例如:

if (!file.IsImage())
{
    ModelState.AddModelError("file", "Upload must be an image");
}

if (ModelState.IsValid)
{
    // save profile and redirect
}

return View(profile);

第一个条件将向
ModelState
添加错误或不添加错误。然后,在第二个条件中,代码将仅在没有错误的情况下运行,然后返回,因此您永远不会点击最终的
返回视图(概要文件)
。然而,如果有任何验证错误,您只需完成最终返回。没有
其他
是必需的,代码更简洁易读。

除了Chris Pratts的回答中指出的代码中的多个错误,您希望执行验证,因此正确的方法是使用实现
IClientValidatable
ValidationAttribute
,以便获得服务器端和客户端验证

验证文件类型的属性示例如下

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class FileTypeAttribute : ValidationAttribute, IClientValidatable
{
    private const string _DefaultErrorMessage = "Only the following file types are allowed: {0}";
    private IEnumerable<string> _ValidTypes { get; set; }

    public FileTypeAttribute(string validTypes)
    {
        _ValidTypes = validTypes.Split(',').Select(s => s.Trim().ToLower());
        ErrorMessage = string.Format(_DefaultErrorMessage, string.Join(" or ", _ValidTypes));
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        HttpPostedFileBase file = value as HttpPostedFileBase;
        if (file != null)
        {
            var isValid = _ValidTypes.Any(e => file.FileName.EndsWith(e));
            if (!isValid)
            {
                return new ValidationResult(ErrorMessageString);
            }
        }
        return ValidationResult.Success;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule
        {
            ValidationType = "filetype",
            ErrorMessage = ErrorMessageString
        };
        rule.ValidationParameters.Add("validtypes", string.Join(",", _ValidTypes));
        yield return rule;
    }
}
然后使用包含文件属性的视图模型并应用该属性

public class ProfileVM
{
    [FileType("jpg, jpeg, gif")] // add allowed types as appropriate
    public HttpPostedFileBase File { get; set; }
}
在我看来

@Html.TextBoxFor(m => m.File, new { type = "file" })
@Html.ValidationMessageFor(m => m.File)

如果启用了客户端验证,您将收到一条错误消息,表单将不会提交。如果禁用它,则
DefaultModelBinder
将添加一个
modelstatererror
错误,
ModelState
将无效,并且可以返回视图。

在debugger中检查它我尝试了控制台调试或什么:),但没有真正理解它。Webapp没有错误!当我上传一个文件,它会保存它和所有,但它会与任何文件。如果我上传一个exe文件,它仍然可以工作。在数据库中,我可以看到mime与图片无关,所以在我的“ProfileController”中,第一行不做任何事情。它应该将文件从概要实例中分离出来,并将其发送给检查,如果它返回为真,那么它应该保存它。感谢您的努力,但这仍然很难理解,因为我是MVC新手。我马上遇到了一个问题,当我尝试将
公共HttpPostedFileBase文件{get;set;}
添加到
概要文件类
时,它就出现了一个错误。我已经将其分为两个项目,域和webui,在
ProfileController
(在webui项目中),我可以通过
使用System.Web使用
HttpPostedFileBase
,但在profile类(域项目)中,它不允许我使用。即使我添加了正确的引用,也不会使用它,只允许使用
HttpPostedFileBaseModelBinder
。我不知道该做什么你缺少一个重要的部分,那就是使用视图模型。参考您不应该在视图中使用数据模型我使用的是
ProfileListViewModel
,但它需要包含profile类中的所有属性,以便使用
ProfileId
。我的主要目标是检查文件仍然不起作用您的
ProfileVM
类应该只包含视图中需要的属性,这意味着它不会包含数据模型中的属性
byte[]ImageData
string ImageMimeType
(您没有编辑它们)但是它将包括
HttpPostedFileBase文件
现在我明白了,我会用那种方式尝试。谢谢你的帮助,我更正了你建议的大部分内容,但仍然不起作用。扩展方法已被更正,else语句已被删除,
ModelState.AddModelError
也已添加,但它仍然无法执行任何操作。如果我尝试上传图片以外的内容,它将保存它而不会出现任何错误。正如我前面提到的,我是MVC新手,但控制器中的
HttpPostedFileBase文件
参数似乎为空。硒