Home > Back-end >  Edit images in wwwroot
Edit images in wwwroot

Time:04-16

I am a programmer at mvc, My goal is to use the image I have in the database and edit it into another image that does not necessarily exist in my wwwroot but in my computer. image: enter image description here

Explanation of the image: I have an image in the database here and I want to edit it Click the edit button Edit image: enter image description here

But when I press the save button I get an error: NullReferenceException: Object reference not set to an instance of an object. PetShop.Client.Services.FileService.File(CreateAnimalViewModel model) in FileService.cs var path = Path.Combine(wwwPath, "Images", model.Photo!.FileName); PetShop.Client.Controllers.AdminController.EditAnimal(CreateAnimalViewModel model) in AdminController.cs await _file.File(model);

Must note that the code of the service does work when I try to add a new image to wwwroot but does not work in edit

My Service:

public class FileService : IFileService
{
    private readonly IWebHostEnvironment _environment;
    public FileService(IWebHostEnvironment environment)
    {
        _environment = environment;
    }
    public async Task<string> File([FromForm] CreateAnimalViewModel model)
    {
        string wwwPath = _environment.WebRootPath;
        var path = Path.Combine(wwwPath, "Images", model.Photo!.FileName);
        if (model.Photo.Length > 0)
        {
            using var stream = new FileStream(path, FileMode.Create);
            await model.Photo.CopyToAsync(stream);
        }
          return model.Animal!.PhotoUrl = model.Photo.FileName;
    }
}
public interface IFileService
{
    Task<string> File([FromForm] CreateAnimalViewModel model);
}

My ViewModel:

public class CreateAnimalViewModel
{
    public Animal? Animal { get; set; }
    public IFormFile Photo { get; set; }
}

My Controller:

public async Task<IActionResult> EditAnimal(int id)
    {
        var animal = await _repo.FindAnimalById(id); 
        ViewBag.Category = new SelectList(_repository.GetCategoriesTable(), "CategoryId", "Name");
        return View(new CreateAnimalViewModel() { Animal = animal});
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> EditAnimal([FromForm] CreateAnimalViewModel model)
    {
        ModelState.Clear();
        TryValidateModel(model);

        await _file.File(model);

        if (!ModelState.IsValid)
        {
            await _repo.EditAnimal(model.Animal!);
            return RedirectToAction(nameof(Manager));
        }
        return View();
    }

My View:

@model PetShop.Client.Models.CreateAnimalViewModel

<div >
<form asp-action="EditAnimal" method="post" >
    <div asp-validation-summary="ModelOnly"></div><input type="hidden" asp-for="Animal!.AnimalId" id="Space"/>
<dl  >
    <dt class = "col-sm-2"><label asp-for="Animal!.Name" id="Space"></label></dt>
    <dd class = "col-sm-10"><input asp-for="Animal!.Name"/><span asp-validation-for="Animal!.Name" ></span></dd>
    <dt class = "col-sm-2"><label asp-for="Animal!.BirthDate" id="Space"></label></dt>
    <dd class = "col-sm-10"><input asp-for="Animal!.BirthDate"/><span asp-validation-for="Animal!.BirthDate"></span></dd>
    <dt class = "col-sm-2"><label asp-for="Animal!.Description" id="Space"></label></dt>
    <dd class = "col-sm-10"><input asp-for="Animal!.Description"/><span asp-validation-for="Animal!.Description"></span></dd>
    <dt class = "col-sm-2"><label asp-for="Animal!.CategoryId" id="Space"></label></dt>
    <dd class = "col-sm-10"><select asp-for="Animal!.CategoryId" asp-items="ViewBag.Category"></select><span asp-validation-for="Animal!.CategoryId"></span></dd>
    <dt class = "col-sm-2"><label asp-for="Photo"></label></dt>
    <dd class = "col-sm-10"><input type="file" asp-for="Photo" accept="image/*"/>
    <span asp-validation-for="Photo"></span></dd>
    <br/> <br/> <br/>
    <input type="submit" value="Save" id="ButtonDesign"/>
</dl>
</form>
    <a asp-action="Commands"><input type="submit" value="Back to Admin Page" id="BackPageButton"/></a>

In view I only show the part of the file all the other things are not relevant to the problem

Edit post Repository:

 public async Task<int> AddAnimal(Animal animal)
    {
        _context.Add(animal!);
        return await _context.SaveChangesAsync();
    }

    public async Task<int> EditAnimal(Animal animal)
    {
        _context.Update(animal);
        return await _context.SaveChangesAsync();
    }

public DbSet<Category> GetCategories()
    {
        var category = _context.Categories;
        return category;
    }

CodePudding user response:

Let's begin with your mistaken code,

[FromForm] In EditAnimal Post Method:

You are submitting [FormBody] but here you are receiving [FromForm] this is one of the reason of your null reference exception. In your case you needn't to use that instead your can directly use class.

Photo has no handlear in controller

You are sending image file from your view. But Your controler doesn't have any handeler for that. It should have IFormFile type to receive image.

So Wrong way:

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> EditAnimal([FromForm] CreateAnimalViewModel model)
    {
        
    }

Note: Based on your correct design and architecture this is not correct way.

Correct way:

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> EditAnimal(CreateAnimalViewModel model, IFormFile photo)
    {
        if (photo == null || photo.Length == 0)
        {
            return Content("File not selected");
        }
        var path = Path.Combine(_environment.WebRootPath, "images", photo.FileName);
        using (FileStream stream = new FileStream(path, FileMode.Create))
        {
            await photo.CopyToAsync(stream);
            stream.Close();
        }
        
        model.Animal!.PhotoUrl = model.Photo!.FileName;
       
        
        // Find the existing data
        var objAnimal = _context.Animals.Where(aId => aId.AnimalId == model.Animal.AnimalId).FirstOrDefault();


       if(model != null)
        {
            //Update the date with new value
            objAnimal!.AnimalId = model.Animal.AnimalId;
            objAnimal.Name = model.Animal.Name;
            objAnimal.Category = model.Animal.Category;
            objAnimal.Description = model.Animal.Description;
            objAnimal.PhotoUrl = model.Animal.PhotoUrl;
            _context.SaveChanges();
        }


        return RedirectToAction("Edit", new { id = model!.Animal.AnimalId });

    }

Edit Method When Loading:

public async Task<IActionResult> Edit(int id)
        {
            var animal = await _context.Animals.FindAsync(id);
            ViewBag.Category = new SelectList(_repository.GetCategoriesTable(), "CategoryId", "Name");
            return View(new CreateAnimalViewModel() { Animal = animal, DisplayPhoto = animal!.PhotoUrl });
          
        }

Note: Here you have done another mistake, there is no point of using CreateAnimalViewModel rather you can directly return Animal Model, as you decided to use CreateAnimalViewModel in that case, the way you are binding Animal domain class to CreateAnimalViewModel you would loose PhotoUrl so here I have introduced another property in View Model to avoid extra modification which I will be using to load image on view. So the updated CreateAnimalViewModel would be:

Updated CreateAnimalViewModel:

public class CreateAnimalViewModel
    {
        public Animal? Animal { get; set; }
        public string? DisplayPhoto { get; set; }
        public IFormFile? Photo { get; set; }
    }

Note: If you can use single ViewModel for this view in that case IFormFile? Photo property is also not required, you can directly bind the image in view. But I didn't modify your existing anything.

View For loading Edit Animal:

@model DotNet6MVCWebApp.Models.CreateAnimalViewModel
<div>
    <form asp-action="EditAnimal" method="post" enctype="multipart/form-data">
        <div asp-validation-summary="ModelOnly"></div><input type="hidden" asp-for="Animal!.AnimalId" id="Space" />
        <div>
            <h4><strong>Animal Details</strong> </h4>

            <table >

                <tr>
                    <th> <label asp-for="Animal!.Name"></label></th>
                    <td> <input asp-for="Animal!.Name"  placeholder="Enter animal name" /><span asp-validation-for="Animal!.Name"></span></td>
                </tr>
                <tr>
                    <th> <label asp-for="Animal!.Description"></label></th>
                    <td> <input asp-for="Animal!.Description"  placeholder="Enter animal description" /><span asp-validation-for="Animal!.Description"></span></td>
                </tr>
                <tr>
                    <th> <label asp-for="Animal!.Category"></label></th>
                    <td> <input asp-for="Animal!.Category"  placeholder="Enter animal category" /><span asp-validation-for="Animal!.Category"></span></td>
                </tr>
                <tr>
                    <th> <label asp-for="Photo"></label></th>
                    <td>
                        <img src="~/images/@Model.Animal!.PhotoUrl"
                             
                             height="50" width="75"
                             style="border:1px"
                             asp-append-version="true" accept="image/*" />
                        <input type="file" name="photo" accept="image/*" />

                    </td>
                </tr>
                <tr>
                    <th>  <button type="submit"  style="width:107px" >Update</button></th>
                    <td> </td>
                </tr>
                <tr>
                    <th>@Html.ActionLink("Back To List", "Index", new { /* id=item.PrimaryKey */ }, new { @class = "btn btn-success" })</th>
                    <td> </td>
                </tr>
            </table>
        </div>
    </form>

</div>

Final Output:

enter image description here

Hope it would guided you accordingly to fix your exception.

  • Related