add custom attribute for file mime type validation #6

Manually merged
wanderer merged 6 commits from feature-custom-attr-validation into master 2021-01-26 15:24:09 +01:00
12 changed files with 98 additions and 14 deletions

@ -38,13 +38,18 @@ namespace pwt_0x01_ng.Areas.Admin.Controllers
[HttpPost] [HttpPost]
public async Task<IActionResult> Create(Product product) public async Task<IActionResult> Create(Product product)
{ {
product.ImageSrc = string.Empty; if (ModelState.IsValid) {
MegaUpload mega_upload = new MegaUpload(hosting_env); product.ImageSrc = string.Empty;
await mega_upload.DoMegaUpload(product); MegaUpload mega_upload = new MegaUpload(hosting_env);
await mega_upload.DoMegaUpload(product);
dbctx.Product.Add(product); dbctx.Product.Add(product);
await dbctx.SaveChangesAsync(); await dbctx.SaveChangesAsync();
return RedirectToAction(nameof(Select)); return RedirectToAction(nameof(Select));
} else {
ViewData["Message"] = "error creating Product";
return View(product);
}
} }
public IActionResult Edit(int id) public IActionResult Edit(int id)

@ -13,4 +13,5 @@
@section Scripts { @section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");} @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script defer src="~/js/validation/file_type.js"></script>
} }

@ -13,4 +13,5 @@
@section Scripts { @section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");} @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script defer src="~/js/validation/file_type.js"></script>
} }

@ -12,22 +12,22 @@
}} }}
<div class="form-group"> <div class="form-group">
<label asp-for="@Model.DataTarget"></label> <label asp-for="@Model.DataTarget"></label>
<input asp-for="@Model.DataTarget" class="form-control" id="inputCarousel" aria-describedby="css selector id of the image" placeholder="Data Target"> <input asp-for="@Model.DataTarget" class="form-control" aria-describedby="css selector id of the image" placeholder="Data Target">
<span asp-validation-for="@Model.DataTarget" class="text-danger"></span> <span asp-validation-for="@Model.DataTarget" class="text-danger"></span>
</div> </div>
<div class="form-group"> <div class="form-group">
<label asp-for="@Model.Image"></label> <label asp-for="@Model.Image"></label>
<input asp-for="@Model.Image" accept="image/*" class="form-inline" id="inputCarousel" aria-describedby="the image"> <input id="file" asp-for="@Model.Image" accept="image/*" class="form-inline" aria-describedby="the image">
<span asp-validation-for="@Model.Image" class="text-danger"></span> <span asp-validation-for="@Model.Image" class="text-danger"></span>
</div> </div>
<div class="form-group"> <div class="form-group">
<label asp-for="@Model.ImageAlt"></label> <label asp-for="@Model.ImageAlt"></label>
<input asp-for="@Model.ImageAlt" class="form-control" id="inputCarousel" aria-describedby="image alt text" placeholder="Image alt"> <input asp-for="@Model.ImageAlt" class="form-control" aria-describedby="image alt text" placeholder="Image alt">
<span asp-validation-for="@Model.ImageAlt" class="text-danger"></span> <span asp-validation-for="@Model.ImageAlt" class="text-danger"></span>
</div> </div>
<div class="form-group"> <div class="form-group">
<label asp-for="@Model.CarouselContent"></label> <label asp-for="@Model.CarouselContent"></label>
<input asp-for="@Model.CarouselContent" class="form-control" id="inputCarousel" aria-describedby="image description" placeholder="Image description"> <input asp-for="@Model.CarouselContent" class="form-control" aria-describedby="image description" placeholder="Image description">
<span asp-validation-for="@Model.CarouselContent" class="text-danger"></span> <span asp-validation-for="@Model.CarouselContent" class="text-danger"></span>
</div> </div>
<button type="submit" class="btn btn-primary">Submit</button> <button type="submit" class="btn btn-primary">Submit</button>

@ -12,4 +12,5 @@
@section Scripts { @section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");} @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script defer src="~/js/validation/file_type.js"></script>
} }

@ -12,4 +12,5 @@
@section Scripts { @section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");} @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script defer src="~/js/validation/file_type.js"></script>
} }

@ -27,7 +27,7 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label asp-for="@Model.Image"></label> <label asp-for="@Model.Image"></label>
<input asp-for="@Model.Image" accept="image/*" class="form-inline" aria-describedby="product image"> <input id="file" asp-for="@Model.Image" accept="image/*" class="form-inline" aria-describedby="product image">
<span asp-validation-for="@Model.Image" class="text-danger"></span> <span asp-validation-for="@Model.Image" class="text-danger"></span>
</div> </div>
<div class="form-group"> <div class="form-group">

@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using pwt_0x01_ng.Models.Validation;
namespace pwt_0x01_ng.Models namespace pwt_0x01_ng.Models
{ {
@ -10,8 +11,8 @@ namespace pwt_0x01_ng.Models
[Required] [Required]
public string DataTarget { get; set; } public string DataTarget { get; set; }
[NotMapped] [NotMapped]
[FileTypeAttr("image")]
public IFormFile Image { get; set; } public IFormFile Image { get; set; }
[Required]
[StringLength(255)] [StringLength(255)]
public string ImageSrc { get; set; } public string ImageSrc { get; set; }
[Required] [Required]

@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using pwt_0x01_ng.Models.Validation;
namespace pwt_0x01_ng.Models namespace pwt_0x01_ng.Models
{ {
@ -13,10 +14,9 @@ namespace pwt_0x01_ng.Models
public int Price { get; set; } public int Price { get; set; }
[Required] [Required]
public string Description { get; set; } public string Description { get; set; }
[Required] [FileTypeAttr("image")]
[NotMapped] [NotMapped]
public IFormFile Image { get; set; } public IFormFile Image { get; set; }
[Required]
[StringLength(255)] [StringLength(255)]
public string ImageSrc { get; set; } public string ImageSrc { get; set; }
[Required] [Required]

@ -0,0 +1,48 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
namespace pwt_0x01_ng.Models.Validation
{
public class FileTypeAttr : ValidationAttribute, IClientModelValidator
{
private readonly string type;
public FileTypeAttr(string type){
this.type = type.ToLower();
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext) {
if (value == null) {
/* img is optional as of now */
return ValidationResult.Success;
} else if (value is IFormFile iff) {
if(iff.ContentType.ToLower().Contains(type)) {
return ValidationResult.Success;
} else {
return new ValidationResult(GetErrorMessage(validationContext.MemberName), new List<string> { validationContext.MemberName });
}
}
throw new NotImplementedException($"Attribute {nameof(FileTypeAttr)} not implemented for object {value.GetType()}.");
}
protected string GetErrorMessage(string member_name) => $"make sure the {member_name} you picked really is of type <code>{type}/*</code>. <a href=\"https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types\" title=\"help\" target=\"_blank\" rel=\"noopener noreferer\"><em>help</em></a>";
public void AddValidation(ClientModelValidationContext ctx){
MergeAttribute(ctx.Attributes, "data-val", "true");
MergeAttribute(ctx.Attributes, "data-val-content", GetErrorMessage("file"));
MergeAttribute(ctx.Attributes, "data-val-content-type", type);
}
private bool MergeAttribute(IDictionary<string, string> attributes, string key, string value){
if (attributes.ContainsKey(key)){
return false;
}
attributes.Add(key, value);
return true;
}
}
}

@ -28,6 +28,7 @@
<Folder Include="Migrations\pgsql" /> <Folder Include="Migrations\pgsql" />
<Folder Include="Models\Database" /> <Folder Include="Models\Database" />
<Folder Include="Models\Database\Conf" /> <Folder Include="Models\Database\Conf" />
<Folder Include="Models\Validation" />
<Folder Include="wwwroot\images\carousels" /> <Folder Include="wwwroot\images\carousels" />
<Folder Include="wwwroot\images\products" /> <Folder Include="wwwroot\images\products" />
</ItemGroup> </ItemGroup>

@ -0,0 +1,25 @@
$.validator.addMethod('content', function (value, element, params) {
var f_type = params[1]
uploaded_type = "";
if (!value) {
return true;
}
if (element && element.files && element.files.length > 0) {
uploaded_type = element.files[0].type;
}
if (f_type && uploaded_type != "" && uploaded_type.toLowerCase().includes(f_type)) {
return true;
}
return false;
});
$.validator.unobtrusive.adapters.add('content', ['type'], function (options) {
var element = $(options.form).find('#file')[0];
options.rules['content'] = [element, options.params['type']];
options.messages['content'] = options.message;
});