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:
Explanation of the image: I have an image in the database here and I want to edit it Click the edit button Edit image:
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 yournull reference exception
. In your case you needn't to use that instead your can directly useclass
.
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 usingCreateAnimalViewModel
rather you can directly returnAnimal Model
, as you decided to useCreateAnimalViewModel
in that case, the way you are bindingAnimal domain class
toCreateAnimalViewModel
you would loosePhotoUrl
so here I have introduced another property inView Model
to avoid extra modification which I will be using to load image onview
. So the updatedCreateAnimalViewModel
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 singleViewModel
for thisview
in that caseIFormFile? Photo property
is also not required, you can directly bind theimage
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:
Hope it would guided you accordingly to fix your exception.