我在这里找到了一个很好的方法来检查用户上传的文件是否是图片,当我尝试实现它时,我怎么遇到问题。
以下是我找到检查文件的课程
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;
}
}
我的个人资料类
public class Profile
{
public int ProfileID { get; set; }
[Required(ErrorMessage = "Please enter a profile name")]
public string Name { get; set; }
[Required(ErrorMessage = "Please enter a intro")]
public string Intro { get; set; }
[Required(ErrorMessage = "Please enter a description")]
public string Description { get; set; }
public decimal Rate { get; set; }
public byte[] ImageData { get; set; }
public string ImageMimeType { get; set; }
}
我更改后的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
方法,但无法实现类中的所有函数,而只是将它分开并按原样使用它。任何想法我哪里出错?
答案 0 :(得分:1)
你有很多问题,一些是重大的,一些是轻微的。
首先,您使用的扩展方法是错误的。添加扩展的重点是它成为该类型实例的方法。 this
关键字参数是隐式的,并且由反向引用调用该方法的对象填充,而不是显式传递。换句话说,你应该对你的条件有什么:
if (file.IsImage())
{
...
另请注意,与true
无法比较。虽然没有错,但这完全没必要,你已经有了一个布尔值。
其次,虽然围绕其余代码放置此条件应该有效防止对象被保存,但它不向用户提供指导。相反,你应该做类似的事情:
if (!file.IsImage())
{
ModelState.AddModelError("file", "Upload must be an image");
}
if (ModelState.IsValid)
{
...
通过向ModelState
添加错误,不仅会导致IsValid
为false,而且现在会在再次返回表单时向用户显示实际的错误消息。
第三,通过尝试从数据库中选择现有的配置文件实例,您将获得该实例或null。因此,您不需要检查ProfileId
是否为0,这是一个相当脆弱的检查(用户只需将隐藏字段的值更改为其他内容即可修改现有项目) 。相反,只需:
var dbEntry = context.Profiles.Find(profile.ProfileID);
if (dbEntry == null)
{
// add profile
}
else
{
// update profile
}
第五,你永远不会对file
做任何事情。在某些时候,你应该做的事情如下:
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
添加错误。然后,在第二个条件中,代码将仅在没有错误的情况下运行,然后返回,因此您永远不会命中最终的return View(profile)
。但是,如果出现任何验证错误,您只需进入最终回报。不需要else
,代码更简洁,更易读。
答案 1 :(得分:1)
除了Chris Pratts回答中指出的代码中的多个错误之外,您希望执行验证,因此正确的方法是使用实现ValidationAttribute
的{{1}}以便您获得两个服务器方和客户方验证。
验证文件类型的属性示例是
IClientValidatable
然后将以下脚本添加到您的视图
[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;
}
}
然后使用包含文件属性的视图模型并应用属性
$.validator.unobtrusive.adapters.add('filetype', ['validtypes'], function (options) {
options.rules['filetype'] = { validtypes: options.params.validtypes.split(',') };
options.messages['filetype'] = options.message;
});
$.validator.addMethod("filetype", function (value, element, param) {
if (!value) {
return true;
}
var extension = getFileExtension(value);
return $.inArray(extension, param.validtypes) !== -1;
});
function getFileExtension(fileName) {
if (/[.]/.exec(fileName)) {
return /[^.]+$/.exec(fileName)[0].toLowerCase();
}
return null;
}
在视图中
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)
将添加ModelStateError
错误,而DefaultModelBinder
将无效,并且可以返回该视图。