How to make the form file not required if the path to files already exists in the DB - asp.net-core

I have a view model set up as:
public class BookViewModel
{
public int Id { get; set; }
[Display(Name = "Book Name")]
public string Name { get; set; }
public string Path { get; set; }
[Display(Name = "Pdf Book")]
public IFormFile BookPdfFile { get; set; }
}
In the view, I am using the view model properties
<form asp-action="CreateEdit" method="post" enctype="multipart/form-data">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input asp-for="Id" type="hidden" />
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
#if (!string.IsNullOrEmpty(Model.BookPath))
{
<div class="form-group">
<label>Uploaded Book:</label>
<a asp-action="Download" asp-route-id="#Model.Id">#Model.Name</a>
</div>
}
<div class="form-group">
<label asp-for="BookPdfFile" class="control-label"></label>
<input class="form-control" type="file" asp-for="BookPdfFile" accept=".pdf">
<span asp-validation-for="BookPdfFile" class="text-danger"></span>
</div>
</form>
Using this View model in the view in this way always asks for the file to be uploaded. If the file is uploaded during the create mode or if it has the file path, how to not ask to make the form file required?
The Book entity is:
public class Book
{
public int Id { get; set; }
[MaxLength(100)]
public string Title { get; set; }
[MaxLength(50)]
public string? Path { get; set; }
public ICollection<BookChapter> Chapters { get; set; }
}
How could I modify my code to make the form file not required if the path already exists in the DB?
On the server side I could add the validation attribute but how would I make that work on the client side as well?
I have yet not worked on posting and saving the files to the controller on HttpPost. So, my controller is just a one-line initializing the view model.
public async Task<IActionResult> CreateBook()
{
BookViewModel bookVM = new BookViewModel();
return View("CreateEdit", bookVM);
}

Related

Asp.net core 6 Html.DropDownListFor System.NullReferenceException

I have two classes "Customer" and "Deals" the customer has a foreign key relationship to the deal. The user should choose from the dropdown list the customer that makes the deal. So I create a ViewModel class that contains a SelectListItem of customers and a deal and update the creat in the deal controller, the error appears when creating the deals:
System.NullReferenceException HResult=0x80004003 Message=Object reference not set to an instance of an object.
-ViewModel:
public class CreateDealVM
{
public Deals deal { get; set; }
public IEnumerable<SelectListItem> selectedCustomer { get; set; }
public int selectedCustomerId { get; set; }
}
}
The DealController methods
public IActionResult Create()
{
Deals deal = new Deals();
List<Customer> customerList = _context.Customers.ToList();
CreateDealVM vm = new CreateDealVM();
vm.selectedCustomer = new SelectList(customerList, "customerId", "customerName");
vm.deal = deal;
return View(vm);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CreateDealVM vm)
{
if (ModelState.IsValid)
{
try
{
vm.deal.customer =_context.Customers.Find(vm.selectedCustomerId);
_context.Deals.Add(vm.deal);
_context.SaveChanges();
return RedirectToAction("Index");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
return View(vm);
}
return View();
}
Deal Model class:
namespace MyShop.Models
{
[Table("Deals")]
public class Deals
{
[Key]
[Display(Name = "ID")]
public int dealId { get; set; }
[ForeignKey("Customer")]
[Display(Name = "Customer")]
public Customer customer { get; set; }
[Display(Name = "CustomerName")]
public string? parentCustomerName { get; set; }
[Display(Name = "product")]
public DealTypeEnum product { get; set; }
[Display(Name = "Date")]
public DateTime saleDate { get; set; }
[Display(Name = "Quantity")]
public float quantity { get; set; }
[Display(Name = "Price")]
public float price { get; set; }
}
The view.cshtml page :
#model MyShop.ViewModels.CreateDealVM
#{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Deals</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
#Html.LabelFor(model => model.selectedCustomer, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.selectedCustomerId, Model.selectedCustomer,"--Select--" )
</div>
</div>
<div class="form-group">
<label asp-for="deal.product" class="control-label"></label>
<select asp-for="deal.product" class="form-control"></select>
<span asp-validation-for="deal.product" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="deal.saleDate" class="control-label"></label>
<input asp-for="deal.saleDate" class="form-control" />
<span asp-validation-for="deal.saleDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="deal.quantity" class="control-label"></label>
<input asp-for="deal.quantity" class="form-control" />
<span asp-validation-for="deal.quantity" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="deal.price" class="control-label"></label>
<input asp-for="deal.price" class="form-control" />
<span asp-validation-for="deal.price" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
I tried this link ASP.NET MVC DropdownlistFor Object reference not set to an instance of an object? but it did not work.
I suppose you have this problem not when you render the Create.cshtml view the first time, but after you press Create button and the ModelState.IsValid in the public ActionResult Create(CreateDealVM vm) is false.
After that you just render the Create.cshtml view by calling
return View();
This is why the Model is null when you trying to render the Create.cshtml view the second time.
You should use debugger to check this and if this is exactly what's happening replace return View(); line by
return View(vm);
In additional analyze your code in the view, to find out why the data model become not valid when passed from view to controller.

Form validation with tag-helper asp.net core 3.1 not show in red

I have a simple web application with create page using form with validations.
.cs code:
public class OneTask
{
public int Id { get; set; }
[Required] public string Title { get; set; }
[Required][MinLength(10)]
public string Description { get; set; }
[Required, Range(1,5)] public int Priority { get; set; }
}
.cshtml code:
<div class="container" style="width:50%">
<div asp-validation-summary="All"></div>
<form asp-page="CreateTask" class="" method="post">
<div class="form-group">
<label asp-for="NewTask.Title">Title</label>
<input asp-for="NewTask.Title" class="form-control" />
// rest of form here...
validation scripts:
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
Instead of showing error validations in red it shows it in black.
---------------------------------------------|
The Title field is required.
The Description field is required.
The value '' is invalid.
---------------------------------------------|
Can you help ?
Just add class="text-danger" to change the text color
<div class="text-danger" asp-validation-summary="All"></div>

.net core modelstate invalid using selectlist with guid

So i am in the process of creating my 3rd crud service inside my webapp. the webapp is running .net core 3.0. my 2 previous crud apps where project and employee. so a user/employee does some work on a project.
so for my 'work' crud service/application in my creation view I need to have a dropdown of all projects and a dropdown for all employees and a view inputs. for employee and project the id is a guid because its unique. But i am seem to having an issue that my modelstateis always invalid.
ps: also my data model so to say it doesn't make any foreign keys for example project id with work id or employee id."
model for work:
public class Work
{
public int Id { get; set; }
[Required]
[StringLength(512, ErrorMessage = "Description cannot exceed 512 characters!")]
public string Description { get; set; }
[Required]
[Display(Name = "Working hours")]
public double WorkingHours { get; set; }
[Required]
[Display(Name = "Employee")]
public Guid EmployeeId { get; set; }
public Employee Employee { get; set; }
[Required]
[Display(Name = "Project")]
public Guid ProjectId { get; set; }
public Projects Project { get; set; }
[Required]
public DateTime dateAdded { get; set; }
}
viewmodel:
public class WorkViewModel
{
public IEnumerable<Projects> projects { get; set; }
public IEnumerable<Employee> employees { get; set; }
public Guid employeeId { get; set; }
public Guid projectId { get; set; }
public Work work { get; set; }
}
create view:
#model TimeSheetTool.ViewModels.WorkViewModel
#{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<div class="row">
<div class="col-sm-4">
<form asp-action="Save">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<select asp-for="projectId" asp-items="#(new SelectList(Model.projects, "Id", "Name"))">
<option>Please select one!</option>
</select>
<span asp-validation-for="projectId" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="work.Description" class="control-labels"></label>
<input asp-for="work.Description" class="form-control" />
<span asp-validation-for="work.Description" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="work.WorkingHours" class="control-labels"></label>
<input asp-for="work.WorkingHours" class="form-control" />
<span asp-validation-for="work.WorkingHours"></span>
</div>
<div class="form-group">
<select asp-for="employeeId" asp-items="#(new SelectList(Model.employees, "Id", "Name"))">
<option>Please select one!</option>
</select>
<span asp-validation-for="employeeId" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-outline-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
What am I doing wrong with this? as you can see in the create view for select list I have got Id i assume it would actually need guid since i'm using guid but then the app won't work at all.
It is because of the Work property in the view model.
public Work work { get; set; }
It has validation, which if not satisfied will cause the invalid model state issue
Add the necessary properties to the view model
public class WorkViewModel {
public IEnumerable<Projects> projects { get; set; }
public IEnumerable<Employee> employees { get; set; }
public Guid employeeId { get; set; }
public Guid projectId { get; set; }
[StringLength(512, ErrorMessage = "Description cannot exceed 512 characters!")]
public string Description { get; set; }
[Display(Name = "Working hours")]
public double WorkingHours { get; set; }
}
and populate them as needed when invoking the action to bind the view
<div class="form-group">
<label asp-for="Description" class="control-labels"></label>
<input asp-for="Description" class="form-control" />
<span asp-validation-for="Description" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="WorkingHours" class="control-labels"></label>
<input asp-for="WorkingHours" class="form-control" />
<span asp-validation-for="WorkingHours"></span>
</div>
The values can be copied over to the model on post

ASP.NET Core 3 Razor Page DataAnnotation Compare attribute returns "Could not find a property named <n>"

I have a ASP.Net Core 3 razor page, a very simple reset password form, which uses a Compare Data Annotation to validate both inputs are the same, however, it doesn't seem to be working. The Required annotation triggers ok, but the Compare always returns ModelState.Valid == false and "Could not find a property named Password." as the error message.
the model is
[BindProperty]
[Required(ErrorMessage = "Password is required.")]
public string Password { get; set; }
[BindProperty]
[Required(ErrorMessage = "Password is required.")]
[Compare(nameof(Password), ErrorMessage = "Passwords don't match")]
public string ConfirmPassword { get; set; }
and the cshtml is
<form method="Post">
<label>New Password</label>
<input asp-for="Password" type="Password" >
<span asp-validation-for="Password" class="text-danger"></span>
<label>Confirm Password</label>
<input asp-for="ConfirmPassword" type="Password" >
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
<button type="Submit">Reset Password</button>
</form>
I've paired back the code to the complete minimum and just can't seem to see what the issue is...
thanks to Kirk Larkin for pointing me to the github issue detailing this. I decided to create a nested viewmodel class to contain the properties. The Compare annotation now works correctly.
nested class looks like this...
[BindProperty]
public ViewModel viewModel { get; set; }
public class ViewModel {
[Required(ErrorMessage = "Password is required.")]
public string Password { get; set; }
[Required(ErrorMessage = "Confirmation Password is required.")]
[Compare(nameof(Password), ErrorMessage = "Passwords don't match.")]
public string ConfirmPassword { get; set; }
}
and the web page now looks like this...
<form method="Post">
<label>New Password</label>
<input asp-for="viewModel.Password" type="Password" >
<span asp-validation-for="viewModel.Password" class="text-danger"></span>
<label>Confirm Password</label>
<input asp-for="viewModel.ConfirmPassword" type="Password" >
<span asp-validation-for="viewModel.ConfirmPassword" class="text-danger"></span>
<button type="Submit">Reset Password</button>
</form>

Insert to many-to-many relationship from view in asp.net core with EF Core

So i have a view in my ASP.net Core application that i Add a new Car Repair and i want also to add Parts in this model. The Database has 3 tables : Repair - Parts - RepairParts.
I have managed to create the new repair but i have no idea how to pass a list of parts from this view to the controller and insert them in the RepairParts Table.
Repair Class
public class Repair
{
[Key]
public int RepairId { get; set; }
public int VehicleId { get; set; }
public string Notes { get; set; }
public string Mileage { get; set; }
public DateTime RepairDate { get; set; }
public string uuid { get; set; }
[ForeignKey("VehicleId")]
public Vehicle Vehicle { get; set; }
public virtual ICollection<RepairParts> Parts { get; set; }
}
Part Class
public class Part
{
public int PartId { get; set; }
public int Code { get; set; }
public string PartCode { get; set; }
public string Type { get; set; }
public string Name { get; set; }
public string Descr { get; set; }
public string Manufacturer { get; set; }
public string uuid { get; set; }
public virtual ICollection<RepairParts> Repairs { get; set; }
}
RepairPart Class
public class RepairParts
{
public int RepairId { get; set; }
public virtual Repair Repair { get; set; }
public int PartId { get; set; }
public virtual Part Part { get; set; }
}
Repair Controller
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult
Create([Bind("VehicleId,Mileage,RepairDate,Notes")] RepairCreateModel repair)
{
try
{
Repair rep = new Repair();
var NewRepair = _mapper.Map(repair, rep);
_repairService.Add(NewRepair);
}
catch (Exception)
{
throw;
}
return Ok();
}
Repair Service
public void Add(Repair repair)
{
string userid = _httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
repair.uuid = userid;
_context.Repairs.Add(repair);
_context.SaveChanges();
}
Repair Create View
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input asp-for="VehicleId" value="#ViewBag.VehicleId" type="hidden" />
<div class="form-group row">
<label class="col-sm-1 col-form-label">Owner</label>
<div class="col-sm-2">
<input type="text" class="form-control" value="#ViewBag.Owner"
readonly>
</div>
</div>
<div class="form-group row">
<label class="col-sm-1 col-form-label">Vehicle</label>
<div class="col-sm-2">
<input type="text" class="form-control" value="#ViewBag.Vehicle"
readonly>
</div>
<label class="col-sm-2 col-form-label">Licence Plates</label>
<div class="col-sm-2">
<input type="text" class="form-control"
value="#ViewBag.LicencePlates" readonly>
</div>
</div>
<div class="form-group row">
<label class="col-sm-1 col-form-label">Mileage</label>
<div class="col-sm-2">
<input asp-for="Mileage" type="text" class="form-control">
</div>
<label class="col-sm-2 col-form-label">Date:</label>
<div class="col-sm-2">
<input asp-for="RepairDate" type="text" class="form-control">
</div>
</div>
<div class="form-group row">
<label class="col-sm-1 col-form-label">Notes</label>
<div class="col-sm-8 row">
<textarea asp-for="Notes" rows="5" class="form-control"></textarea>
</div>
</div>
<div class="form-group row">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
Anyone has any idea how can i achieve that?