在.NET 6 MVC网站上,我正在使用cropper.js进行裁剪照片。它可以在我的本地机器上使用,但在我的服务器(IIS 10)上不起作用

问题描述 投票:0回答:0

@using Microsoft.AspNetCore.Identity @using Memayo.ViewModels @using Microsoft.AspNetCore.Http @using System.Security.Claims @inject SignInManager<AppUser> SignInManager @inject UserManager<AppUser> UserManager @inject IHttpContextAccessor HttpContextAccessor @{ var userId = HttpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier); var user = userId != null ? await UserManager.FindByIdAsync(userId) : null; var avatarUrl = user?.Avatar != null ? $"/images/uploads/small/{user.Avatar}" : "/images/default-avatar.png"; } <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>@ViewData["Title"] - Memayo</title> <link rel="stylesheet" href="~/bootstrap/css/bootstrap.min.css" /> <link rel="stylesheet" href="~/css/site.css" /> <link href="~/css/memayo.css" rel="stylesheet" /> @* <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-eval';"> *@ @* <link href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.12/cropper.min.css" rel="stylesheet" /> *@ <link href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.6.2/cropper.min.css" rel="stylesheet" /> </head> <body> <header> <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3"> <div class="container"> <a class="nav-linkxx xxtext-dark" asp-controller="Home" asp-action="Index"> <img class="memayologo" src="~/images/logo.png" /> </a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between"> <ul class="navbar-nav flex-grow-1"> @if (SignInManager.IsSignedIn(User)) { @if (User.IsInRole("admin")) { <li class="nav-item"> <a class="nav-link text-lightx text-danger" asp-area="venus" asp-controller="AHome" asp-action="Index">Admin Area</a> </li> } else { <li class="nav-item"> <a class="nav-link" asp-controller="Yarn" asp-action="Index">Yarn</a> </li> <li class="nav-item"> <a class="nav-link" asp-controller="Question" asp-action="Index">Chapters</a> </li> <li class="nav-item"> <a class="nav-link" asp-controller="Photo" asp-action="Index">Photos</a> </li> <li class="nav-item"> <a class="nav-link" asp-controller="YourQuestion" asp-action="Index">My Q&A</a> </li> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false">Friends</a> <ul class="dropdown-menu"> <li><a class="dropdown-item" asp-controller="Friend" asp-action="Index">Friends List</a></li> <li><a class="dropdown-item" asp-controller="Friend" asp-action="Requests">Friend Requests</a></li> <li><a class="dropdown-item" asp-controller="Friend" asp-action="Search">Search for Friends</a></li> </ul> </li> } } </ul> <ul class="navbar-nav"> @if (SignInManager.IsSignedIn(User)) { <li class="nav-item dropdown"> <a class="nav-link d-flex align-items-center gap-2" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false"> <div class="rounded-circle overflow-hidden" style="width: 38px; height: 38px;"> @if (user?.Avatar != null) { <img src="@avatarUrl" alt="User Avatar" class="w-100 h-100 object-fit-cover" /> } else { <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" class="w-100 h-100"> <circle cx="64" cy="64" r="64" fill="#E2E8F0" /> <path d="M64 36c7.732 0 14 6.268 14 14s-6.268 14-14 14-14-6.268-14-14 6.268-14 14-14zM64 70c18.778 0 34 8.222 34 20v8c0 2-2 2-2 2H32s-2 0-2-2v-8c0-11.778 15.222-20 34-20z" fill="#94A3B8" /> </svg> } </div> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chevron-down" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z" /> </svg> </a> <ul class="dropdown-menu dropdown-menu-end"> <li><a class="dropdown-item" asp-controller="MyProfile" asp-action="Profile">Profile</a></li> <li><a class="dropdown-item" asp-controller="MyProfile" asp-action="Avatar">Avatar</a></li> <li><hr class="dropdown-divider"></li> <li><a class="dropdown-item" asp-controller="account" asp-action="Logout">Logout</a></li> </ul> </li> } else { <li class="nav-item"> <a class="nav-link text-dark" asp-controller="account" asp-action="Register">Register</a> </li> <li class="nav-item"> <a class="nav-link text-dark" asp-controller="account" asp-action="Login">Login</a> </li> } </ul> </div> </div> </nav> </header> <div class="container"> <main role="main" class="pb-3"> <partial name="_MessagePartial" /> @RenderBody() </main> </div> <footer class="border-top footer text-muted"> <div class="container"> <partial name="_VersionPartial" /> <a asp-controller="Home" asp-action="Privacy">Privacy</a> | <a asp-controller="Home" asp-action="Terms">Terms</a> </div> </footer> <script src="~/js/jquery/dist/jquery.min.js"></script> <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script> <script src="~/bootstrap/js/bootstrap.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.12/cropper.min.js"></script> @await RenderSectionAsync("Scripts", required: false) </body> </html>

Controller:

using CSharpAwsSesServiceHelper.EmailService; using Memayo.Models; using Memayo.ViewModels; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using System.Threading.Tasks; using System.Security.Claims; using Microsoft.AspNetCore.Authentication; using System.Collections.Generic; using Microsoft.Extensions.Hosting; using Memayo.Repositories; using Microsoft.AspNetCore.Http; using System.IO; using System.Linq; using System; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Processing; namespace Memayo.Controllers { [Authorize(Roles = "user")] public class MyProfileController : Controller { private readonly ILogger<MyProfileController> _logger; private IWebHostEnvironment _env; private SignInManager<AppUser> _signManager; private UserManager<AppUser> _userManager; private readonly IEmailService _emailService; private readonly IUserRepository _userRepository; private readonly IHttpContextAccessor _httpContextAccessor; private string userId => _httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value; public MyProfileController(ILogger<MyProfileController> logger, IWebHostEnvironment env, UserManager<AppUser> userManager, SignInManager<AppUser> signManager, IEmailService emailService, IUserRepository userRepository, IHttpContextAccessor httpContextAccessor) { _logger = logger; _env = env; _userManager = userManager; _signManager = signManager; _emailService = emailService; _userRepository = userRepository; _httpContextAccessor = httpContextAccessor; } public IActionResult Index() { return View(); } public async Task<IActionResult> Profile() { var user = await _userRepository.GetAsync(userId); return View(user); } [HttpPost] public async Task<IActionResult> Profile(User user) { await _userRepository.UpdateProfileAsync(user); TempData["message"] = "Profile Updated"; return RedirectToAction("Index", "Home"); } [Authorize(Roles = "user")] public async Task<IActionResult> Avatar() { var user = await _userRepository.GetAsync(userId); return View(user); } [HttpPost] [Authorize(Roles = "user")] public async Task<IActionResult> Avatar(IFormFile avatar) { try { if (avatar == null) { _logger.LogError("Avatar is null"); return Json(new { success = false, error = "No file received" }); } _logger.LogInformation($"Received file: {avatar.FileName}, Length: {avatar.Length}, ContentType: {avatar.ContentType}"); if (avatar.Length == 0) { TempData["messageError"] = "Please upload a valid image."; return Json(new { success = false, error = "Empty file" }); } // Check file type var allowedExtensions = new[] { ".png", ".jpg", ".jpeg" }; var extension = Path.GetExtension(avatar.FileName).ToLower(); if (!allowedExtensions.Contains(extension)) { TempData["messageError"] = "Only PNG and JPG images are allowed."; return Json(new { success = false, error = "Invalid file type" }); } // Delete old avatar if exists, get user var user = await _userRepository.GetAsync(userId); var uploadsDir = Path.Combine(_env.WebRootPath, "images", "uploads"); var filename = $"{Guid.NewGuid()}{extension}"; var oldAvatarPathLarge = Path.Combine(uploadsDir, "large", user.Avatar); var oldAvatarPathSmall = Path.Combine(uploadsDir, "small", user.Avatar); var newAvatarPathLarge = Path.Combine(uploadsDir, "large", filename); var newAvatarPathSmall = Path.Combine(uploadsDir, "small", filename); _logger.LogInformation($"Paths: New Large: {newAvatarPathLarge}, New Small: {newAvatarPathSmall}"); // Delete old avatar if exists if (System.IO.File.Exists(oldAvatarPathLarge)) { try { System.IO.File.Delete(oldAvatarPathLarge); } catch (Exception ex) { _logger.LogError($"Error deleting old large avatar: {ex.Message}"); } } if (System.IO.File.Exists(oldAvatarPathSmall)) { try { System.IO.File.Delete(oldAvatarPathSmall); } catch (Exception ex) { _logger.LogError($"Error deleting old small avatar: {ex.Message}"); } } // Resize the image using (var image = await Image.LoadAsync(avatar.OpenReadStream())) { _logger.LogInformation("Image loaded successfully"); var maxWidthLarge = 1000; var maxHeightLarge = 1000; var maxWidthSmall = 200; var maxHeightSmall = 200; var largeImage = image.Clone(ctx => ctx.Resize(new ResizeOptions { Size = new SixLabors.ImageSharp.Size(maxWidthLarge, maxHeightLarge), Mode = ResizeMode.Max })); var smallImage = image.Clone(ctx => ctx.Resize(new ResizeOptions { Size = new SixLabors.ImageSharp.Size(maxWidthSmall, maxHeightSmall), Mode = ResizeMode.Max })); _logger.LogInformation("Images resized"); try { if (extension == ".png") { await largeImage.SaveAsync(newAvatarPathLarge, new PngEncoder()); await smallImage.SaveAsync(newAvatarPathSmall, new PngEncoder()); } else { await largeImage.SaveAsync(newAvatarPathLarge, new JpegEncoder()); await smallImage.SaveAsync(newAvatarPathSmall, new JpegEncoder()); } _logger.LogInformation("Images saved successfully"); } catch (Exception ex) { _logger.LogError($"Error saving images: {ex.Message}\nStack: {ex.StackTrace}"); throw; } } // Update the user's avatar path in the database user.Avatar = filename; await _userRepository.UpdateAvatarAsync(user); _logger.LogInformation("Database updated successfully"); TempData["message"] = "Avatar updated successfully."; return Json(new { success = true }); } catch (Exception ex) { _logger.LogError($"Avatar upload error: {ex.Message}\nStack: {ex.StackTrace}"); return Json(new { success = false, error = ex.Message }); } } } }

视图:
@model Memayo.Models.User
@{
    ViewData["Title"] = "Edit Avatar";
}
<h2>Edit Avatar</h2>
<div class="avatar-section">
    <h4>Current Avatar</h4>
    <div class="current-avatar">
        @if (!string.IsNullOrEmpty(Model.Avatar))
        {
            <img src="/images/uploads/large/@Model.Avatar" alt="User Avatar" class="rounded-circle" style="max-width: 200px; max-height: 200px;" />
        }
        else
        {
            <p>No avatar uploaded. Please upload an avatar.</p>
        }
    </div>
</div>
<hr />
<div class="upload-section">
    @if (TempData["message"] != null)
    {
        <div class="alert alert-success">@TempData["message"]</div>
    }
    @if (TempData["messageError"] != null)
    {
        <div class="alert alert-danger">@TempData["messageError"]</div>
    }
    <form id="avatarForm" method="post" enctype="multipart/form-data">
        <div class="form-group">
            <label for="avatar">Select New Avatar</label>
            <input type="file" class="form-control" id="avatar" name="avatar" accept=".jpg,.jpeg,.png" />
            <small class="form-text text-muted">Allowed: .jpg, .png</small>
        </div>
        <div class="form-group">
            <img id="preview" class="img-fluid" />
        </div>
        <button type="button" class="btn btn-primary" id="cropButton">Crop and Upload Avatar</button>
        <a asp-controller="Home" asp-action="Index" class="btn btn-secondary">Cancel</a>
    </form>
</div>

@section Scripts {
    <script type="module">
        // Initialize state
        const state = {
            cropper: null,
            uploadUrl: '@Url.Action("Avatar", "MyProfile")',
            redirectUrl: '@Url.Action("Index", "Home")',
        };

        // Configuration object for Cropper
        const cropperConfig = {
            aspectRatio: 1,
            viewMode: 1,
            autoCropArea: 1,
        };

        // Handle file input change
        function handleFileChange(event) {
            const image = document.getElementById('preview');
            const file = event.target.files[0];

            if (!file) return;

            const objectURL = URL.createObjectURL(file);
            image.src = objectURL;

            image.addEventListener('load', () => {
                if (state.cropper) {
                    state.cropper.destroy();
                }

                // Initialize the cropper with config object
                state.cropper = new Cropper(image, cropperConfig);
            }, { once: true }); // Use once: true to prevent memory leaks
        }

        // Handle crop and upload
        async function handleCropAndUpload() {
            if (!state.cropper) {
                alert('Please select an image to crop.');
                return;
            }

            try {
                const canvas = state.cropper.getCroppedCanvas({
                    width: 1000,
                    height: 1000
                });

                if (!canvas) {
                    throw new Error('Failed to generate canvas');
                }

                const blob = await new Promise((resolve) => {
                    canvas.toBlob(resolve, 'image/png', 0.9);
                });

                if (!blob) {
                    throw new Error('Failed to generate image blob');
                }

                const formData = new FormData();
                formData.append('avatar', new File([blob], 'avatar.png', {
                    type: 'image/png'
                }));

                const response = await fetch(state.uploadUrl, {
                    method: 'POST',
                    body: formData
                });

                const data = await response.json();

                if (data.success) {
                    window.location.href = state.redirectUrl;
                } else {
                    throw new Error(data.error || 'Upload failed');
                }
            } catch (error) {
                console.error('Upload error:', error);
                alert(`An error occurred: ${error.message}`);
            }
        }

        // Add event listeners
        document.getElementById('avatar').addEventListener('change', handleFileChange);
        document.getElementById('cropButton').addEventListener('click', handleCropAndUpload);
    </script>
}

我在警报中获取此错误:

eRROR上传阿凡达:值不能为null。 (参数'Path3')

我在浏览器工具中也会遇到此错误

内容安全策略(CSP)可防止跨站点脚本 通过阻止脚本和样式表的内联执行来攻击。

要解决此问题,移动所有内联脚本(例如OnClick = [JS代码])和 样式进入外部文件。

⚠️允许内联执行有可能通过 注入HTML脚本元素。如果您绝对必须 允许内联脚本和样式:

将不安全的内线作为CSP标头的来源,添加哈希或 内联脚本的nonce到您的CSP标头。 1指令 指令元素源位置状态 Script-SRC-Elem Avatar:119封锁了解更多:内容安全性 策略 - 内联代码

我问过克劳德和聊天gpt 40和O1。他们建议我将此元标记放入其中,但没有帮助

<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-eval';">

这样做可能会导致安全问题。

我有一种感觉与不安全的CSP标头有关,但Google搜索一无所获。

我可以在服务器上使用此功能?

thanks

对于CSP错误,您正在使用您的应用程序使用外部JavaScript资源文件,并且在您的应用程序中未列出该文件。
如果您想避免获得错误,唯一的方法就是可以在应用程序代码或IIS服务器上设置CSP标头,则可以将自定义标头添加到网站。为了避免安全问题,您可以将标题设置为特定域。

或您可以复制文件内容并在项目中创建新文件并使用它。 错误“值不能为null”,请确保您的文件夹在下面可用。

javascript asp.net-core iis
最新问题
© www.soinside.com 2019 - 2024. All rights reserved.