Validation failed: InvalidRoleName - .net-core

I have some problem with the code. I'm not able to create a role. The code seems fine to me, but I'm not able to find the error. With WriteLine I'm getting the following message:
Microsoft.AspNetCore.Identity.RoleManager: Warning: Role 38676182-f211-47d7-b69b-f190e4338123 validation failed: InvalidRoleName.
Why InvalidRoleName is invalid? It's just a string. Any help is appreciated.
c#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Sarpinos.Pages.Admin
{
public class CreateRoleModel : PageModel
{
private readonly RoleManager<IdentityRole> _roleManager;
public CreateRoleModel(RoleManager<IdentityRole> roleManager)
{
_roleManager = roleManager;
}
[BindProperty]
public List<IdentityRole> Roles { get; set; }
public void OnGet()
{
}
public async Task<IActionResult> OnPost()
{
if (ModelState.IsValid)
{
var newRole = new IdentityRole();
newRole.Id = Guid.NewGuid().ToString();
await _roleManager.CreateAsync(newRole);
Console.WriteLine($"{newRole.Id} {newRole.Name}");
}
return RedirectToPage("Dashboard");
}
}
}
cshtml:
#page
#model Sarpinos.Pages.Admin.CreateRoleModel
#{
ViewData["Title"] = "Register";
}
<div class="container">
<form asp-page="CreateRole" method="POST">
<div class="form-group">
<label asp-for="#Model.Roles">Role name:</label>
<input type="text" asp-for="#Model.Roles" class="form-control" placeholder="Enter role name" />
<span asp-validation-for="#Model.Roles" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-success">Create</button>
</form>
</div>

[BindProperty]
public List<IdentityRole> Roles { get; set; }
public void OnGet()
{
}
public async Task<IActionResult> OnPost()
{
if (ModelState.IsValid)
{
var newRole = new IdentityRole();
newRole.Id = Guid.NewGuid().ToString();
await _roleManager.CreateAsync(newRole);
Console.WriteLine($"{newRole.Id} {newRole.Name}");
}
return RedirectToPage("Dashboard");
}
According to your description, it seems that you want to create a new Role, but by using the above code, it looks that you are using a List<IdentityRole> to display the roles and in the post method, you didn't set the role name for the newRole. So, it might cause the issue.
To Create new Role, in the CreateRoleModel, you could create a new class which contains the Role related properties. For example:
public class CreateRoleModel : PageModel
{
private readonly RoleManager<IdentityRole> _roleManager;
public CreateRoleModel(RoleManager<IdentityRole> roleManager) { _roleManager = roleManager; }
public class NewRole
{
public string RoleName { get; set; }
}
[BindProperty]
public NewRole Input { get; set; }
public void OnGet()
{
Input = new NewRole(); //
}
public async Task<IActionResult> OnPostAsync()
{
if (ModelState.IsValid)
{
var newRole = new IdentityRole();
newRole.Id = Guid.NewGuid().ToString();
newRole.Name = Input.RoleName;
await _roleManager.CreateAsync(newRole);
Console.WriteLine($"{newRole.Id} {newRole.Name}");
}
return RedirectToPage("Home/Index");
}
}
Then, in the CreateRole.cshtml page, based on the NewRole class to insert a new role (you could change the namespace to yours).
#page
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
#model IdentitySample.Pages.CreateRoleModel
<div class="container">
<form method="POST">
#Html.AntiForgeryToken()
<div class="form-group">
<label asp-for="#Model.Input.RoleName">Role name:</label>
<input asp-for="#Model.Input.RoleName" class="form-control" placeholder="Enter role name" />
<span asp-validation-for="#Model.Input.RoleName" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-success">Create</button>
</form>
</div>

Related

How to fix Invalid OperationException: Multiple constructors accepting all given argument types have been found in type 'System.Collections.Generic

I have seen variations to this question asked. But none of the answers seem to help me.
When trying to view a list of roles in my view after hitting the AddOrRemoveUsers button(see picture)
I get the following error message.
I cant find where the multiple constructors error seem to be. Am I missing something.
#page
#using ThreeTierAdvisementApp.Areas.Identity.Pages.Account.Administration
#using ThreeTierAdvisementApp.Data
#model List<UserRole>
<form method="post">
<div class="card">
<div class="card-header">
<h2>Add or remove users from this role</h2>
</div>
<div class="card-body">
#for(int i = 0; i<Model.Count; i++){
<div class="form-check m-1">
<input asp-for="#Model[i].IsSelected" class="form-check-input" />
<label class="form-check-label">
#Model[i].UserName
</label>
</div>
}
</div>
<div class="card-footer">
<input type="submit" value="Update" class="btn btn-primary"
style="width:auto" />
<a asp-action="EditRole" asp-route-id="UserId"
class="btn btn-primary" style="width:auto">Cancel</a>
</div>
</div>
</form>
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using ThreeTierAdvisementApp.Data;
namespace ThreeTierAdvisementApp.Areas.Identity.Pages.Account.Administration
{
public class EditUsersInRoleModel : PageModel
{
private readonly RoleManager<IdentityRole> _roleManager;
private readonly UserManager<DefaultUser> _userManager;
public EditUsersInRoleModel(RoleManager<IdentityRole> roleManager, UserManager<DefaultUser> userManager)
{
_roleManager = roleManager;
_userManager = userManager;
}
[BindProperty]
public UserRole RoleView { get; set; }
public async Task<IActionResult> OnGet(string roleId)
{
RoleView = new UserRole { UserId = roleId };
var role = await _roleManager.FindByIdAsync(roleId);
if (role == null)
{
return NotFound();
}
var model = new List<UserRole>();
foreach (var user in _userManager.Users)
{
var userRoleViewModel = new UserRole
{
UserId = user.Id,
UserName = user.UserName,
};
if (await _userManager.IsInRoleAsync(user, role.Name))
{
userRoleViewModel.IsSelected = true;
}
else
{
userRoleViewModel.IsSelected = false;
}
model.Add(userRoleViewModel);
}
return Page();
}
}
}
using Microsoft.AspNetCore.Identity;
using System.Security.Principal;
namespace ThreeTierAdvisementApp.Data
{
public class UserRole
{
public string UserId { get; set; }
public string UserName { get; set; }
public bool IsSelected { get; set; }
}
}
I am using asp.net 6 Razor page pattern but all the examples online are using the MVC pattern. Would appreciate some feedback on how to handle this.

Blazor WebAssembly with identity server passing username through form

I am new to Blazor WebAssembly. I have a simple page that allows users to register a company. It takes the company name, description and the username. At the moment the username is entered by the user but I want to automatically grab the username of the logged in user and post it without the need of user input. Here is my page:
#page "/companies/create"
#attribute [Authorize]
#inject HttpClient Http
#inject NavigationManager Navigation
<h3>Register your company</h3>
<AuthorizeView>
Hello, #context.User.Identity.Name!
</AuthorizeView>
<EditForm Model="Companies" OnValidSubmit="#HandleValidSubmit">
<DataAnnotationsValidator />
<div class="form-group">
<label class="control-label">Name</label>
<InputText #bind-Value="Companies.Name" class="form-control" />
<ValidationMessage For="#(() => Companies.Name)" />
</div>
<div class="form-group">
<label class="control-label">Description</label>
<InputText #bind-Value="Companies.Description" class="form-control" />
<ValidationMessage For="#(() => Companies.Description)" />
</div>
<div class="form-group">
<label class="control-label">Username</label>
<InputText #bind-Value="Companies.Username" class="form-control"/>
<ValidationMessage For="#(() => Companies.Username)" />
</div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> Register
</button>
</EditForm>
#code {
private Sprelo.Shared.Companies Companies { get; set; } = new Sprelo.Shared.Companies();
private async void HandleValidSubmit()
{
try
{
var response = await Http.PostAsJsonAsync($"/api/companies", Companies);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
var companies = JsonConvert.DeserializeObject<Sprelo.Shared.Companies>(content);
Navigation.NavigateTo($"Companies/edit/{companies.Id}");
}
catch (AccessTokenNotAvailableException exception)
{
exception.Redirect();
}
catch (Exception e)
{
}
}
}
This is my model:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Sprelo.Shared
{
public class Companies
{
[Key]
public Guid Id { get; set; }
[Required]
public String Name { get; set; }
public String Description { get; set; }
public String Username { get; set; }
}
}
and this is an auto generated API controller
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Sprelo.Server.Data;
using Sprelo.Shared;
namespace Sprelo.Server.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class CompaniesController : ControllerBase
{
private readonly ApplicationDbContext _context;
public CompaniesController(ApplicationDbContext context)
{
_context = context;
}
// GET: api/Companies
[HttpGet]
public async Task<ActionResult<IEnumerable<Companies>>> GetCompanies()
{
return await _context.Companies.ToListAsync();
}
// GET: api/Companies/5
[HttpGet("{id}")]
public async Task<ActionResult<Companies>> GetCompanies(Guid id)
{
var companies = await _context.Companies.FindAsync(id);
if (companies == null)
{
return NotFound();
}
return companies;
}
// PUT: api/Companies/5
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
[HttpPut("{id}")]
public async Task<IActionResult> PutCompanies(Guid id, Companies companies)
{
if (id != companies.Id)
{
return BadRequest();
}
_context.Entry(companies).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!CompaniesExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
// POST: api/Companies
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
[HttpPost]
public async Task<ActionResult<Companies>> PostCompanies(Companies companies)
{
_context.Companies.Add(companies);
await _context.SaveChangesAsync();
return CreatedAtAction("GetCompanies", new { id = companies.Id }, companies);
}
// DELETE: api/Companies/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteCompanies(Guid id)
{
var companies = await _context.Companies.FindAsync(id);
if (companies == null)
{
return NotFound();
}
_context.Companies.Remove(companies);
await _context.SaveChangesAsync();
return NoContent();
}
private bool CompaniesExists(Guid id)
{
return _context.Companies.Any(e => e.Id == id);
}
}
}
I have been struggling with this for quite a while, any help would be appriciated!
Inject the AuthenticationStateProvider object into your component like this:
#inject AuthenticationStateProvider authProvider
Override the OnInitialized life cycle method:
protected override void OnInitialized ()
{
var authenticationStateTask = await
authProvider.GetAuthenticationStateAsync();
var user = authenticationStateTask.User;
// Check if the user is authenticated
if (user.Identity.IsAuthenticated)
{
// Grab the user name and assign it to Companies.Username
Companies.Username = user.Identity.Name
}
}

Asp.net core uploading file and storing file url to database

I have the code working in a new seperate test project how ever it will not work with my excisting work project. I am trying to upload a file as part of a form, store it in a folder in wwwroot and store the url in a database. I have posted the code below.The program compiles and runs but does not store anything when create button is pressed. any help would be greatly appreciated.
//Model
namespace PostProjectEvaluations.Web.Models
{
public partial class Projects
{
[Key]
public int ProjectId { get; set; }
[Required]
[StringLength(300)]
public string Name { get; set; }
[Required]
[StringLength(50)]
public string Manager { get; set; }
public string FilePath { get; set; }
}
public class ProjectsVM
{
public string Name { get; set; }
public IFormFile File { get; set; }
}
//Controller
namespace PostProjectEvaluations.Web.Controllers
{
public class projectsController : Controller
{
private readonly IApplicationRepository ApplicationRepository;
private readonly PostProjectEvaluationsContext _context;
private IHostingEnvironment mxHostingEnvironment { get; set; }
private object objproject;
public projectsController(IApplicationRepository applicationRepository,
IHostingEnvironment hostingEnvironment, PostProjectEvaluationsContext context)
{
mxHostingEnvironment = hostingEnvironment;
ApplicationRepository = applicationRepository;
_context = context;
}
public IActionResult Index()
{
ViewBag.dataSource = ApplicationRepository.GetAllProjects().ToList();
var projects = ApplicationRepository.GetAllProjects();
return View(projects);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Index(ProjectsVM projectsVM)
{
if (projectsVM.File != null)
{
//upload files to wwwroot
var fileName = Path.GetFileName(projectsVM.File.FileName);
var filePath = Path.Combine(mxHostingEnvironment.WebRootPath, "Uploads", fileName);
using (var fileSteam = new FileStream(filePath, FileMode.Create))
{
await projectsVM.File.CopyToAsync(fileSteam);
}
//your logic to save filePath to database, for example
Projects projects = new Projects();
projects.Name = projectsVM.Name;
projects.FilePath = filePath;
_context.Projects.Add(projects);
_context.SaveChanges();
}
else
{
}
return View("Index");
}
public IActionResult Details(int id)
{
var project = ApplicationRepository.GetProjects(id);
return View(project);
}
[HttpGet]
public IActionResult Create()
{
var project = new Projects();
return View(project);
}
[HttpPost]
public IActionResult Create(Projects projects)
{
ApplicationRepository.Create(projects);
return RedirectToAction("Index");
}
public IActionResult Delete(int id)
{
var project = ApplicationRepository.GetProjects(id);
ApplicationRepository.Delete(project);
return RedirectToAction("Index");
}
[HttpGet]
public IActionResult Edit(int id)
{
var project = ApplicationRepository.GetProjects(id);
//mxApplicationRepository.SaveChangesAsync();
return View(project);
}
[HttpPost]
public IActionResult Edit(Projects projects)
{
ApplicationRepository.Edit(projects);
//mxApplicationRepository.SaveChangesAsync();
return RedirectToAction("Index");
}
}
}
//View
<form enctype="multipart/form-data" asp-controller="Projects" asp-action="Create" method="post" class="form-horizontal">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-body">
<h3 class="form-section">Project Info</h3>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label class="control-label col-md-3">Project Name</label>
<div class="col-md-9">
<input asp-for="Name" class="form-control" placeholder="Name">
<span asp-validation-for="Name" class="text-danger"></span>
</div>
</div>
</div>
<!--/span-->
<div class="col-md-6">
<div class="form-group">
<label class="control-label col-md-3">Project Manager</label>
<div class="col-md-9">
<input asp-for="Manager" class="form-control" placeholder="Name">
</div>
</div>
</div>
<!--/span-->
</div>
<!--/span-->
</div>
<h3 class="form-section">Project Files</h3>
<!--/row-->
<div class="row">
<div>
<div class="col-md-6">
<div class="col-md-10">
<p>Upload one or more files using this form:</p>
<input type="file" name="files" multiple />
</div>
</div>
</div>
<div>
</div>
</div>
<div class="form-actions right">
<input type="submit" class="btn blue-assembly" name="submitButton" value="Save" />
<a asp-action="Index" class="btn default" onclick="cancelClick()">Cancel</a>
</div>
Include your model in view as in the first line
#ProjectsVM
You have to give the same name as a model
Try this:
<input type="file" id= name="File" multiple />

asp.net core upload file as part of a form

Trying to upload a file as part of a form and store it in a folder in wwwroot then store the url for the file in a sql server database table along with the other details of the form? If anyone has any ideas it would be greatly appriciated. I have used this code but it does not store anything. However when i created a fresh new project i managed to get the code working on that correctly but cannot seem to get it to work in this work project. Not sure if it has something to do with the fact i have repositories and the fresh project i created that did work didnt have any repositories. That is the only difference i can think of. Any ideas?
//Model
namespace PostProjectEvaluations.Web.Models
{
public partial class Projects
{
[Key]
public int ProjectId { get; set; }
[Required]
[StringLength(300)]
public string Name { get; set; }
[Required]
[StringLength(50)]
public string Manager { get; set; }
public string FilePath { get; set; }
}
public class ProjectsVM
{
public string Name { get; set; }
public IFormFile File { get; set; }
}
//Controller
namespace PostProjectEvaluations.Web.Controllers
{
public class projectsController : Controller
{
private readonly IApplicationRepository ApplicationRepository;
private readonly PostProjectEvaluationsContext _context;
private IHostingEnvironment mxHostingEnvironment { get; set; }
private object objproject;
public projectsController(IApplicationRepository applicationRepository,
IHostingEnvironment hostingEnvironment, PostProjectEvaluationsContext context)
{
mxHostingEnvironment = hostingEnvironment;
ApplicationRepository = applicationRepository;
_context = context;
}
public IActionResult Index()
{
ViewBag.dataSource = ApplicationRepository.GetAllProjects().ToList();
var projects = ApplicationRepository.GetAllProjects();
return View(projects);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Index(ProjectsVM projectsVM)
{
if (projectsVM.File != null)
{
//upload files to wwwroot
var fileName = Path.GetFileName(projectsVM.File.FileName);
var filePath = Path.Combine(mxHostingEnvironment.WebRootPath, "Uploads", fileName);
using (var fileSteam = new FileStream(filePath, FileMode.Create))
{
await projectsVM.File.CopyToAsync(fileSteam);
}
//your logic to save filePath to database, for example
Projects projects = new Projects();
projects.Name = projectsVM.Name;
projects.FilePath = filePath;
_context.Projects.Add(projects);
_context.SaveChanges();
}
else
{
}
return View("Index");
}
public IActionResult Details(int id)
{
var project = ApplicationRepository.GetProjects(id);
return View(project);
}
[HttpGet]
public IActionResult Create()
{
var project = new Projects();
return View(project);
}
[HttpPost]
public IActionResult Create(Projects projects)
{
ApplicationRepository.Create(projects);
return RedirectToAction("Index");
}
public IActionResult Delete(int id)
{
var project = ApplicationRepository.GetProjects(id);
ApplicationRepository.Delete(project);
return RedirectToAction("Index");
}
[HttpGet]
public IActionResult Edit(int id)
{
var project = ApplicationRepository.GetProjects(id);
//mxApplicationRepository.SaveChangesAsync();
return View(project);
}
[HttpPost]
public IActionResult Edit(Projects projects)
{
ApplicationRepository.Edit(projects);
//mxApplicationRepository.SaveChangesAsync();
return RedirectToAction("Index");
}
}
}
//View
<form enctype="multipart/form-data" asp-controller="Projects" asp-action="Create" method="post" class="form-horizontal">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-body">
<h3 class="form-section">Project Info</h3>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label class="control-label col-md-3">Project Name</label>
<div class="col-md-9">
<input asp-for="Name" class="form-control" placeholder="Name">
<span asp-validation-for="Name" class="text-danger"></span>
</div>
</div>
</div>
<!--/span-->
<div class="col-md-6">
<div class="form-group">
<label class="control-label col-md-3">Project Manager</label>
<div class="col-md-9">
<input asp-for="Manager" class="form-control" placeholder="Name">
</div>
</div>
</div>
<!--/span-->
</div>
<!--/span-->
</div>
<h3 class="form-section">Project Files</h3>
<!--/row-->
<div class="row">
<div>
<div class="col-md-6">
<div class="col-md-10">
<p>Upload one or more files using this form:</p>
<input type="file" name="files" multiple />
</div>
</div>
</div>
<div>
</div>
</div>
<div class="form-actions right">
<input type="submit" class="btn blue-assembly" name="submitButton" value="Save" />
<a asp-action="Index" class="btn default" onclick="cancelClick()">Cancel</a>
</div>
</form>
You could use IFormFile to receive the file.And then save the file path url to your database using EF core. Remember to create a myFiles folder where to save uploaded files under wwwroot firstly.
You could refer to the tutorial of File uploads in ASP.NET Core
Below is a simple demo:
Models:
public class Engineer
{
public int Id { get; set; }
public string Name { get; set; }
public string FilePath { get; set; }
}
public class EngineerVM
{
public string Name { get; set; }
public IFormFile File{ get; set; }
}
View:
#model EngineerVM
<form method="post" enctype="multipart/form-data" asp-controller="Home" asp-action="Index">
<div class="form-group">
<div class="col-md-10">
<input type="text" asp-for="Name" />
</div>
<div class="col-md-10">
<p>Upload one or more files using this form:</p>
<input type="file" name="file"/>
</div>
</div>
<div class="form-group">
<div class="col-md-10">
<input type="submit" value="Save" />
</div>
</div>
</form>
Controller:
public class HomeController : Controller
{
private readonly IHostingEnvironment _hostingEnv;
private readonly ApplicationDbContext _context;
public HomeController(IHostingEnvironment hostingEnv,ApplicationDbContext context)
{
_hostingEnv = hostingEnv;
_context = context;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Index(EngineerVM engineerVM)
{
if (engineerVM.File != null)
{
//upload files to wwwroot
var fileName = Path.GetFileName(engineerVM.File.FileName);
var filePath = Path.Combine(_hostingEnv.WebRootPath, "images", fileName);
using (var fileSteam = new FileStream(filePath, FileMode.Create))
{
await engineerVM.File.CopyToAsync(fileSteam);
}
//your logic to save filePath to database, for example
Engineer engineer = new Engineer();
engineer.Name = engineerVM.Name;
engineer.FilePath = filePath;
_context.Engineers.Add(engineer);
await _context.SaveChangesAsync();
}
else
{
}
return View();
}
}
Try the following code
if (ModelState.IsValid)
{
string folder = null;
if (model.File != null)
{
if (model.File.Length > 0)
{
Guid guid1 = Guid.NewGuid();
folder = "/attachement/docs/" + guid1 + model.File.FileName;
string uploadFolder = Path.Combine(webHostEnvironment.WebRootPath, "attachement/docs");
string uniqueFileName = guid1 + model.File.FileName;
string filePath = Path.Combine(uploadFolder, uniqueFileName);
using (var stream = new FileStream(filePath, FileMode.Create))
{
await model.FormFile.CopyToAsync(stream);
}
}
}}
And for uploading multiple files, first you have to define this in Model
public List<IFormFile> ImageFiles { get; set; }
and in Model constructor
ImageFiles = new List<IFormFile>();
and then use the following code
foreach (var file in model.ImageFiles)
{
if (file.Length > 0)
{
Guid guid = Guid.NewGuid();
folder = "/attachement/images/" + guid + file.FileName;
string uploadFolder = Path.Combine(webHostEnvironment.WebRootPath, "attachement/images");
string uniqueFileName = guid + file.FileName;
string filePath = Path.Combine(uploadFolder, uniqueFileName);
// string filePath = Path.Combine(uploadFolder, model.FormFile.FileName);
using (var stream = new FileStream(filePath, FileMode.Create))
{
await file.CopyToAsync(stream);
}
await context.GraspProgressDetails.AddAsync(new GraspProgressDetail
{
GraspProgressId = result.Entity.ProgressId,
ImageUrl = folder
});
}
}

.net core 2.1 validation state: invalid

The following simple .NET Core 2.1 MVC code reports "Validation State: Invalid" when I submit to create. Everything works fine without the Owner property; and it works if Owner property is not required.
The Owner is the current user which is in the context of the server side, and it shouldn't be submitted from a client side, so the Create.cshtml doesn't have a Owner input in the form.
The error:
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Executing action method AnnouncementApp.Controllers.AnnouncementsController.Create (AnnouncementApp) with arguments (AnnouncementApp.Models.Announcement) - Validation state: Invalid
The model:
using System;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using AnnouncementApp.Models.Attributes;
using Microsoft.AspNetCore.Identity;
//using System.Security.Claims;
namespace AnnouncementApp.Models
{
public class Announcement
{
public int ID { get; set; }
[Required]
public string Content { get; set; }
[Display(Name = "Start Date and Time")]
public DateTime StartDate { get; set; }
[StartEndDate("End Date and Time must be after Start Date and Time")]
[Display(Name = "End Date and Time")]
public DateTime EndDate { get; set; }
[Required]
[BindNever]
public IdentityUser Owner { get; set; }
}
}
The controller method:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("ID,Content,StartDate,EndDate")] Announcement announcement)
{
if (ModelState.IsValid)
{
var user = await _userManager.GetUserAsync(this.User);
announcement.Owner = user;
_context.Add(announcement);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(announcement);
}
The Create.cshtml
#model AnnouncementApp.Models.Announcement
#{
ViewData["Title"] = "Create";
}
<h2>Create</h2>
<h4>Announcement</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">
<label asp-for="Content" class="control-label"></label>
<textarea asp-for="Content" class="form-control"></textarea>
<span asp-validation-for="Content" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="StartDate" class="control-label"></label>
<input asp-for="StartDate" class="form-control" />
<span asp-validation-for="StartDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="EndDate" class="control-label"></label>
<input asp-for="EndDate" class="form-control" />
<span asp-validation-for="EndDate" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
For Announcement, it will apply [Required] for both client validation and database table.
As the comments indicates, you could consider split Announcement to Db Model and ViewModel, you could define a new AnnouncementViewModel for client validation.
For another option, you could try configure the [Required] in the fluent api instead of attribute.
Here are the detail steps.
Change Announcement
public class Announcement
{
public int ID { get; set; }
[Required]
public string Content { get; set; }
[Display(Name = "Start Date and Time")]
public DateTime StartDate { get; set; }
public string OwnerId { get; set; }
//[Required]
[BindNever]
[ForeignKey("OwnerId")]
public IdentityUser Owner { get; set; }
}
Fluent api in ApplicationDbContext
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Announcement>()
.Property(a => a.OwnerId)
.IsRequired();
}
Controller
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("ID,Content,StartDate")] Announcement announcement)
{
if (ModelState.IsValid)
{
var user = await _userManager.GetUserAsync(User);
announcement.Owner = user;
_context.Add(announcement);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(announcement);
}
I am not 100 % sure, what you define as the issue, but if you want to supress the "Model Invalid" error, since you are always setting the Owner property through the HttpContext, you can use the following before validating the model:
ModelState["Owner"].ValidationState = ModelValidationState.Valid
I think your issue is that you tell the router to never bind "Owner", but you still tells it is required, and therefore the ModelState would potentially invalidate it.
As long as the "Required" annotation is used, I do not think the ModelState will validate without it being set correctly.
Example:
ModelState["Owner"].ValidationState = ModelValidationState.Valid
if (ModelState.IsValid)
{
var user = await _userManager.GetUserAsync(this.User);
announcement.Owner = user;
_context.Add(announcement);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(announcement);

Resources