I have a problem with my App.
I have This Multiple Model:
public class MultipleModel
{
public Staff Staff;
public RegisterModel RegisterModel;
}
public class RegisterModel
{
[Required]
[Display(Name = "User name")]
public string UserName { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
public partial class Staff
{
public Staff()
{
this.Tables = new HashSet<Tables>();
}
public int EmployeeID { get; set; }
public string First_name { get; set; }
public string Last_name { get; set; }
public string Telephone { get; set; }
public string Description { get; set; }
public string Username { get; set; }
public virtual ICollection<Tables> Tables { get; set; }
}
The controler is:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult MultipleModelView(MultipleModel model)
{
if (ModelState.IsValid)
{
try
{
WebSecurity.CreateUserAndAccount(model.RegisterModel.UserName, model.RegisterModel.Password);
Roles.AddUserToRole(model.RegisterModel.UserName, "Customer");
Staff staff = new Staff();
staff.Username = model.RegisterModel.UserName;
staff.First_name = model.Staff.First_name;
staff.Last_name = model.Staff.Last_name;
staff.Telephone = model.Staff.Telephone;
db.Staff.Add(staff);
db.SaveChanges();
WebSecurity.Login(model.RegisterModel.UserName, model.RegisterModel.Password);
return RedirectToAction("Index", "Home");
}
catch (MembershipCreateUserException e)
{
ModelState.AddModelError("", ErrorCodeToString(e.StatusCode));
}
}
return View(model);
}
And View is:
#model test13.Models.MultipleModel
#{
ViewBag.Title = "MultipleModelView";
}
<h2>MultipleModelView</h2>
#using (Html.BeginForm("MultipleModelView", "Account", FormMethod.Post))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary()
<fieldset>
<legend>Registration Form</legend>
<ol>
<li>
#Html.LabelFor(m => m.RegisterModel.UserName)
#Html.TextBoxFor(m => m.RegisterModel.UserName)
</li>
<li>
#Html.LabelFor(m => m.RegisterModel.Password)
#Html.PasswordFor(m => m.RegisterModel.Password)
</li>
<li>
#Html.LabelFor(m => m.RegisterModel.ConfirmPassword)
#Html.PasswordFor(m => m.RegisterModel.ConfirmPassword)
</li>
<li>
#Html.LabelFor(m => m.Staff.First_name)
#Html.TextBoxFor(m => m.Staff.First_name)
</li>
<li>
#Html.LabelFor(m => m.Staff.Last_name)
#Html.TextBoxFor(m => m.Staff.Last_name)
</li>
<li>
#Html.LabelFor(m => m.Staff.Telephone)
#Html.TextBoxFor(m => m.Staff.Telephone)
</li>
</ol>
<input type="submit" value="Register" />
</fieldset>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
When i Fill data to the TextBoxs and Post them, App get me Exception in this part of code:
WebSecurity.CreateUserAndAccount(model.RegisterModel.UserName, model.RegisterModel.Password);
System.NullReferenceException: Object reference not set to an instance of an object.
Help me plese with this. Thank you.
Use properties and not fields in your view model:
public class MultipleModel
{
public Staff Staff { get; set; }
public RegisterModel RegisterModel { get; set; }
}
The default model binder in ASP.NET MVC works only with properties. Fields are ignored.
initializes register model in constructor
public class MultipleModel
{
public MultipleModel()
{
this.RegisterModel = new RegisterModel();
}
public Staff Staff{get;set;};
public RegisterModel RegisterModel{get;set;};
}
Related
Using ASP.Net Core, I am developing a simple form to reset the password using email address.
My model is as follow:
public class EmailViewModel
{
[Required(ErrorMessageResourceName = "Model_RequireEmail", ErrorMessageResourceType = typeof(Resources.Identity))]
[Display(Name = "Models_Account_Email", ResourceType = typeof(Resources.Identity))]
[DataType(DataType.EmailAddress)]
[EmailAddress(ErrorMessage = "Models.EmailFormat")]
[MaxLength(100, ErrorMessage = "Models.MaxLength")]
public string? Email { get; set; }
}
And my view:
<form method="post">
<div class="uk-text-danger">
#Html.ValidationSummary()
</div>
<div class="uk-margin">
<div class="uk-inline">
<span class="uk-form-icon" uk-icon="icon: mail"></span>
#Html.TextBoxFor(m => m.Data.EmailViewModel.Email, new
{
#class = "validate uk-input",
placeholder = Html.DisplayNameFor(m => m.Data.EmailViewModel.Email)
})
#Html.HiddenFor(m => m.Data.ResetPasswordAction)
#Html.HiddenFor(m => m.Data.ResetPasswordController)
</div>
</div>
<button class="uk-button uk-button-default">#Html.LocalizedIdentity("Views.Account.PassReset")</button>
</form>
My issue is that even if I provide a correct email address, I keep getting the message of the "required" data annotation. What did I miss?
EDIT 13/12/2021
The issue occurs when I used a nested ViewModel. For example, the viewModel associated with my form is
public class ForgotPasswordViewModel
{
public EmailViewModel EmailViewModel => new EmailViewModel();
[HiddenInput]
public string? ResetPasswordAction { get; set; }
[HiddenInput]
public string? ResetPasswordController { get; set; }
}
using the EmailViewModel above. This give a ModelState.IsValid= false because the email is null even if provided.
If I bypass the EmailViewModel and use for my ForgotPasswordViewMode, the following:
public class ForgotPasswordViewModel
{
[Display(Name = "Models_Account_Email", ResourceType = typeof(Resources.Identity))]
[Required]
[DataType(DataType.EmailAddress)]
[EmailAddress(ErrorMessage = "Models.EmailFormat")]
[MaxLength(100, ErrorMessage = "Models.MaxLength")]
public string? Email { get; set; }
[HiddenInput]
public string? ResetPasswordAction { get; set; }
[HiddenInput]
public string? ResetPasswordController { get; set; }
}
It works nicely.
The problem is that I want to use the nested ViewModel to avoid repeating it.
Why doesn't it work with the nested EmailViewModel?
This is my model and my test view, it will lead to ModelState.IsValid == false because the input form should have a property of emailVM.Email but not EmailViewModel.
public class EmailViewModel
{
//[Required(ErrorMessageResourceName = "Model_RequireEmail", ErrorMessageResourceType = typeof(Resources.Identity))]
//[Display(Name = "Models_Account_Email", ResourceType = typeof(Resources.Identity))]
[Display(Name = "Models_Account_Email")]
[Required]
[DataType(DataType.EmailAddress)]
[EmailAddress(ErrorMessage = "Models.EmailFormat")]
[MaxLength(100, ErrorMessage = "Models.MaxLength")]
public string? Email { get; set; }
}
public class ForgotPasswordViewModel
{
//public EmailViewModel emailVM => new EmailViewModel();
public EmailViewModel emailVM { get; set; }
[HiddenInput]
public string? ResetPasswordAction { get; set; }
[HiddenInput]
public string? ResetPasswordController { get; set; }
}
<form method="POST" asp-action="Edit">
<div class="uk-text-danger">
#Html.ValidationSummary()
</div>
<div class="uk-inline">
<span class="uk-form-icon" uk-icon="icon: mail"></span>
#Html.TextBoxFor(m => m.emailVM.Email, new
{
placeholder = Html.DisplayNameFor(m => m.emailVM.Email)
})
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
Using latest ASP.NET Core, Windows 10 Pro
TLDR: my controller is returning a "Microsoft.AspNetCore.Mvc.ModelBinding.ModelError" for an entity's parent.
Entity: Schedule
public class Schedule
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ScheduleId { get; set; }
[Required]
[Display(Name = "Start On")]
public DateTime StartDateTime { get; set; } = DateTime.Now.AddMinutes(1);
[Display(Name = "Reschedule On")]
public string Recurrence { get; set; }
[Display(Name = "Parameter 1")]
[MaxLength(256), StringLength(256)]
public string Param1 { get; set; }
// ... more properties
[Display(Name = "Parameter 7")]
[MaxLength(256), StringLength(256)]
public string Param7 { get; set; }
[Display(Name = "Message Text")]
public string MessageText { get; set; }
#region relationships
[Required]
[Display(Name = "For Process")]
public int ProcessId { get; set; }
[Required]
[Display(Name = "For Process")]
public virtual Process Process { get; set; }
public virtual ICollection<ScheduleNotification> ScheduleNotifications { get; set; }
#endregion
}
Entity: Process
public class Process : IValidatableObject
{
private int? _ApplicationId;
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int ProcessId { get; set; }
[Required]
[StringLength(80, MinimumLength = 5, ErrorMessage = "Names must be between 5 and 80 characters")]
[MaxLength(80)]
public string Name { get; set; }
public string Description { get; set; }
[Required]
[Display(Name = "Process Type")]
public ProcessTypeEnum ProcessType { get; set; }
// ... more properties
[Display(Name = "Primary Application")]
public int? ApplicationId
{
get
{
return _ApplicationId;
}
set
{
_ApplicationId = value == 0 ? null : value;
}
}
public Application Application { get; set; }
public ICollection<Schedule> Schedules { get; set; }
}
Controller Action on Create Get
public IActionResult Create(int? processId)
{
// no value provided, so redirect to view that prompts for process
if (!processId.HasValue || processId == 0)
{
TempData["Error"] = "You must select a process to schedule";
return RedirectToAction("Processes");
}
var schedule = new Schedule()
{
ProcessId = processId.GetValueOrDefault(),
Process = _context.Processes.Find(processId.GetValueOrDefault()),
ScheduleNotifications = new List<ScheduleNotification>()
};
return View(schedule);
}
Razor view called on create
#model HangfireServer.Models.Schedule
#{
ViewData["Title"] = "Create";
}
<h1>Schedule</h1>
<hr />
<div class="row">
<div class="col">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="text-danger" style="direction:rtl" asp-validation-summary="All"></div>
#Html.HiddenFor(m => m.ProcessId)
#Html.HiddenFor(m => m.Process)
<div class="form-group row">
<text class="control-label col-sm-2">#Html.DisplayNameFor(m => m.Process.Name)</text>
<div class="col-sm-4">
#Html.DisplayFor(m => m.Process.Name)
</div>
<text class="text-left col-sm-6 text-secondary">Process to schedule</text>
</div>
<div class="form-group row">
<label asp-for="StartDateTime" class="control-label col-sm-2"></label>
<div class="col-sm-4">
<input asp-for="StartDateTime" class="control-label" />
<span asp-validation-for="StartDateTime" class="text-danger"></span>
</div>
<text class="text-left col-sm-6 text-secondary">Date and time to initiate process</text>
</div>
<!-- more stuff -->>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
Create post controller
// POST: Schedules/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("ScheduleId,StartDateTime,Recurrence,Param1,Param2,Param3,Param4,Param5,Param6,Param7,ProcessId,Process,ScheduleNotifications")] Schedule schedule)
{
if (ModelState.IsValid)
{
// save the new schedule
schedule.Process = await _context.Processes.FindAsync(schedule.ProcessId);
_context.Add(schedule);
await _context.SaveChangesAsync();
// queue the new schedule
EnqueueProcess(schedule);
TempData["Success"] = $"Process {schedule.Process.Name} has been scheduled";
return RedirectToAction(nameof(Index));
}
// display additional errors not caught in page validation
string errorMessage = "";
foreach (var modelState in ViewData.ModelState.Values)
{
foreach (var error in modelState.Errors)
{
errorMessage = String.IsNullOrEmpty(errorMessage) ? error.ToString() : "<br>" + error.ToString();
}
}
TempData["Error"] = errorMessage;
// return to current view
return RedirectToAction("Create", new { processId = schedule.ProcessId });
}
On trace, the GET is passing to the view schedule with its parent process id and process object populated. On POST
ModelState.IsValid = False
schedule.processId = 2 (or whatever the value sent from GET was
schedule.process is null (even though there's a hidden field for process in vie)
ModelState only has 1 invalid key
SubKey={Process}, Key="Process", ValidationState=Invalid
I think I figured it out - but this is just a hack, there's probably a correct way to do it
// populate process property with an instance of a process object
schedule.Process = await _context.Processes.Include(p => p.Application).FirstOrDefaultAsync(p => p.ProcessId == schedule.ProcessId);
ModelState.Clear();
Then either
ModelState.Remove("Process");
Or
TryValidateModel(schedule);
Regardless ModelState.IsValid is now true
So I have this problem when trying to save an item to the database in my asp.net mvc4 web app.
I have three classes listed below
public class Posts
{
[Key]
public int PostID { get; set; }
[Required(ErrorMessage="A Title is required for your Post")]
[Display(Name="Title")]
public string PostTitle { get; set; }
[Required(ErrorMessage="This Field is Required")]
[Display(Name = "Post")]
public string PostContent { get; set; }
[Required()]
[DataType(DataType.DateTime)]
public DateTime PostDate { get; set; }
//public int AuthorID { get; set; }
//public int CommentID { get; set; }
[NotMapped]
public virtual List<Comments> Comment { get; set; }
public virtual Users user { get; set; }
}
and this class has a many to one relationship with the users class below
public class Users
{
public Users()
{
}
[Key]
public int UserID { get; set; }
[Required()]
public string Firstname { get; set; }
[Required()]
public string Lastname { get; set; }
[Required()]
[DataType(DataType.Date, ErrorMessage="Enter a Valid Date")]
public DateTime DOB { get; set; }
//[Required()]
//public string Photo { get; set; }
[Required()]
public string Sex { get; set; }
[Required()]
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
public DateTime RegDate { get; set; }
[Required()]
[Column("Username")]
//[Remote("doesUserNameExist", "Account", HttpMethod = "POST", ErrorMessage = "User name has already been taken. Please enter a different User name.")]
public string Username { get; set; }
[Required()]
[DataType(DataType.Password)]
public string Password { get; set; }
public string PasswordSalt { get; set; }
[System.ComponentModel.DataAnnotations.Compare("Password", ErrorMessage="Passwords do not match")]
[DataType(DataType.Password)]
//[NotMapped]
//public string ConfirmPassword { get; set; }
public virtual List<Posts> Post { get; set; }
public virtual List<Comments> Comment { get; set; }
}
The database table for the Posts.cs class has a field called user_UserID which i assume is to store the id of the user that creates a post. I'm trying to save the posts to the database using the below code
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Posts</legend>
<div class="form-group">
#Html.LabelFor(model => model.PostTitle)
#Html.TextBoxFor(model => model.PostTitle, new {#class = "form-control"})
#Html.ValidationMessageFor(model => model.PostTitle)
</div>
<div class="form-group">
#Html.LabelFor(model => model.PostContent)
#Html.TextAreaFor(model => model.PostContent, new {#class = "form-control"})
#Html.ValidationMessageFor(model => model.PostContent)
</div>
<input type="hidden" name="user.UserID" value="#Session["LoggedUserID"]" />
<div>
<input type="submit" value="Submit" />
</div>
</fieldset>
}
As you can see, the user ID i'm saving to the database table is gotten from the user ID stored in the Session["LoggedUserID"] variable. The controller that handles the saving is below
public class PostsController : Controller
{
//
// GET: /Posts/
BlogContext db = new BlogContext();
public ActionResult Index()
{
return View();
}
[HttpGet]
public ActionResult Create()
{
return View();
}
[HttpPost]
public ActionResult Create(Posts Post)
{
var errors = ModelState.Values.SelectMany(v => v.Errors);
try
{
if (ModelState.IsValid)
{
var newPost = db.Post.Create();
newPost.PostTitle = Post.PostTitle;
newPost.PostContent = Post.PostContent;
newPost.PostDate = DateTime.Now;
newPost.user.UserID = Post.user.UserID;
db.Post.Add(newPost);
db.SaveChanges();
return RedirectToAction("Index", "Posts");
}
else
{
ModelState.AddModelError("", "Something is wrong with the model");
}
}
catch (NullReferenceException ex)
{
Debug.WriteLine("Processor Usage" + ex.Message);
}
return View();
}
}
I attached a breakpoint to the Modelstate.IsValid line and then debugged, but i noticed the ModelState is always evaluating to false and the ModelState.AddModelError is showing that is there is something wrong with validating the model. Ive tried all possible tweakings all to no avail. I need help. How can i save the Posts to the database table without this problem.
Please what am i doing wrong?
I suspect ModelState is invalid because you are posting a value for user.UserId (the hidden input) which is initializing the property User which is invalid because of the validation attributes applied to other properties of User. It would be better to create a view model for creating new Posts that contain only the properties you need to display/edit. It is not necessary to include a property for User (the author of the post) since this can be set in the controller when you save the data (similar to what you are doing for the PostDate property
View model
public class PostVM
{
[Required(ErrorMessage="A Title is required for your Post")]
[Display(Name="Title")]
public string Title { get; set; }
[Required(ErrorMessage="This Field is Required")]
[Display(Name = "Post")]
public string Content { get; set; }
}
Controller
[HttpGet]
public ActionResult Create()
{
PostVM model = new PostVM();
return View(model);
}
[HttpPost]
public ActionResult Create(PostVM model)
{
if(!ModelState.IsValid)
{
return View(model);
}
// Initialize new Posts
// Map properties from the view model, set the user and date
// Save and redirect
}
View
#model PostVM
#using (Html.BeginForm()) {
....
<div class="form-group">
#Html.LabelFor(model => model.Title)
#Html.TextBoxFor(model => model.Title, new {#class = "form-control"})
#Html.ValidationMessageFor(model => model.Title)
</div>
<div class="form-group">
#Html.LabelFor(model => model.Content)
#Html.TextAreaFor(model => model.Content, new {#class = "form-control"})
#Html.ValidationMessageFor(model => model.Content)
</div>
<input type="submit" value="Submit" />
}
I'm tryng to be more precise to my previous question which can be found here, I got some nice answers but couldn't figure out how to use it in my situation Previous question
I got some nice answers but couldn't figure out how to use it in my situation.
basically I want to have registration page which contains
Email //Comes from my AspNetUser(datamodel) class, also AspNetUsers table exists in database.
UserName//Comes from my AspNetUser(datamodel) class, also AspNetUsers table exists in database.
Password//Comes from my AspNetUser(datamodel) class, also AspNetUsers table exists in database.
Role//dropdownlist, comes from Role(datamodel) class, also Roles table exists in database
In my controller I have impelmented my Register method in following way:
public class AccountController : Controller
{
//private readonly IDbContext dbContext;
//
// GET: /Account/
[HttpGet]
public ActionResult Login()
{
return View();
}
[HttpPost]
[AllowAnonymous]
public ActionResult Login(LoginModel model)
{
if(Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
return RedirectToAction("Index", "Home");
}
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
[HttpGet]
public ActionResult Register()
{
string [] roles = Roles.GetAllRoles();
return View(roles);
}
[HttpPost]
public ActionResult Register(AspNetUser model)
{
return View();
}
}
in my get method i'm passing the roles to view and right now i'm using AspNetUser as model in View
#model Sorama.CustomAuthentiaction.Models.AspNetUser
#{
ViewBag.Title = "Register";
Layout = "~/Views/shared/_BootstrapLayout.empty.cshtml";
}
#section Styles{
<link href="#Url.Content("~/Content/bootstrap.css")" rel="stylesheet" type="text/css" />
}
<div class ="form-signin">
#using (Html.BeginForm("Login", "Account"))
{
#Html.ValidationSummary(true)
<h2 class="form-signin-heading"> Register </h2>
<div class ="input-block-level">#Html.TextBoxFor(model=>model.Email, new{#placeholder = "Email"})</div>
<div class ="input-block-level">#Html.TextBoxFor(model=>model.UserName, new{#placeholder = "UserName"})</div>
<div class ="input-block-level">#Html.PasswordFor(model=>model.Password, new{#placeholder ="Password"})</div>
<div class ="input-block-level">#Html.DropdownlistFor(.....//don't no how to generate dropdownlist)
<button class="btn btn-large btn-primary" type="submit">Sign In</button>
}
</div>
can u tell me how to get that dropdownlist and how can I pass that selected value to controller to use it so that i can put user in role during registration? Would it be better to create new model for Registration?
Edit: AspNetUser model
public class AspNetUser
{
private ICollection<Role> _roles= new Collection<Role>();
public Guid Id { get; set; }
[Required]
public virtual String Username { get; set; }
public virtual String Email { get; set; }
[Required, DataType(DataType.Password)]
public virtual String Password { get; set; }
public virtual String FirstName { get; set; }
public virtual String LastName { get; set; }
[DataType(DataType.MultilineText)]
public virtual String Comment { get; set; }
public virtual Boolean IsApproved { get; set; }
public virtual int PasswordFailuresSinceLastSuccess { get; set; }
public virtual DateTime? LastPasswordFailureDate { get; set; }
public virtual DateTime? LastActivityDate { get; set; }
public virtual DateTime? LastLockoutDate { get; set; }
public virtual DateTime? LastLoginDate { get; set; }
public virtual String ConfirmationToken { get; set; }
public virtual DateTime? CreateDate { get; set; }
public virtual Boolean IsLockedOut { get; set; }
public virtual DateTime? LastPasswordChangedDate { get; set; }
public virtual String PasswordVerificationToken { get; set; }
public virtual DateTime? PasswordVerificationTokenExpirationDate { get; set; }
public virtual ICollection<Role> Roles
{
get { return _roles; }
set { _roles = value; }
}
}
You'd better have a view model specifically designed for this view. Think of what information you need in the view and define your view model:
public class RegisterViewModel
{
public string Email { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public string SelectedRole { get; set; }
public IEnumerable<SelectListItem> Roles { get; set; }
}
As you can see from this view model, in order to have a dropdown list you need 2 properties: one scalar property that will hold the selected value and one collection property to hold the list of available values.
and then:
public ActionResult Register()
{
string [] roles = Roles.GetAllRoles();
var model = new RegisterViewModel();
model.Roles = roles.Select(r => new SelectListItem
{
Value = r,
Text = r,
});
return View(model);
}
[HttpPost]
public ActionResult Register(RegisterViewModel model)
{
// the model.SelectedRole will contain the selected value from the dropdown
// here you could perform the necessary operations in order to create your user
// based on the information stored in the view model that is passed
// NOTE: the model.Roles property will always be null because in HTML,
// a <select> element is only sending the selected value and not the entire list.
// So if you intend to redisplay the same view here instead of redirecting
// makes sure you populate this Roles collection property the same way we did
// in the GET action
return Content("Thanks for registering");
}
and finally the corresponding view:
#model RegisterViewModel
#{
ViewBag.Title = "Register";
Layout = "~/Views/shared/_BootstrapLayout.empty.cshtml";
}
#section Styles{
<link href="#Url.Content("~/Content/bootstrap.css")" rel="stylesheet" type="text/css" />
}
<div class ="form-signin">
#using (Html.BeginForm("Login", "Account"))
{
#Html.ValidationSummary(true)
<h2 class="form-signin-heading"> Register </h2>
<div class ="input-block-level">
#Html.TextBoxFor(model => model.Email, new { placeholder = "Email" })
</div>
<div class ="input-block-level">
#Html.TextBoxFor(model => model.UserName, new { placeholder = "UserName" })
</div>
<div class ="input-block-level">
#Html.PasswordFor(model => model.Password, new { placeholder = "Password" })
</div>
<div class ="input-block-level">
#Html.DropdownlistFor(model => model.SelectedRole, Model.Roles)
</div>
<button class="btn btn-large btn-primary" type="submit">Sign In</button>
}
</div>
I'm trying to make a simple news system in MVC4. I'm very new at it, and I have a simple News base class that looks like this:
NewsPost:
public class NewsPost
{
public virtual int Id { get; set; }
public virtual string Subject { get; set; }
public virtual string Content { get; set; }
}
It's then attached to a category class like so:
NewsCategory:
public class NewsCategory
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<NewsPost> NewsPosts { get; set; }
}
I then have a controller for creating these NewsPosts:
NewsController:
private readonly INewMvcSiteDataSource _db;
public NewsController(INewMvcSiteDataSource db)
{
_db = db;
}
[HttpGet]
public ActionResult Create()
{
var model = new CreateNewsViewModel();
model.Categories = new SelectList(_db.NewsCategories, "Id", "Name");
return View(model);
}
[HttpPost]
public ActionResult Create(CreateNewsViewModel newsModel)
{
if (ModelState.IsValid)
{
int id = int.Parse(newsModel.SelectedCategories.Single(f => f.Selected).Value);
}
return View(newsModel);
}
And lastly, to facilitate with creating the NewsPost, I use a CreateNewsViewModel, like so:
CreateNewsViewModel:
public class CreateNewsViewModel
{
[Required]
public string Subject { get; set; }
[Required]
public string Content { get; set; }
public SelectList Categories { get; set; }
public SelectList SelectedCategories { get; set; }
}
My view looks like this:
Create.cshtml:
#model NewMvcSite.Models.CreateNewsViewModel
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>CreateNewsViewModel</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Subject)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Subject)
#Html.ValidationMessageFor(model => model.Subject)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Content)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Content)
#Html.ValidationMessageFor(model => model.Content)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Categories)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.Categories, Model.SelectedCategories)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
For some reason, my viewmodel isn't returned when I click the submit button to create the newspost, and since there is no parameterless constructor for [HttpPost] Create action, it fails with "No parameterless constructor defined for this object."
I've been trying going through other posts here stating the same problem, but I fail to see the connection between what they are doing and what I am doing. I hope there is someone out there who can help me.
Thank you.
If you want to select category from drop down list, first of all you should add property to your model to hold Id of selected category. Something like this (property Category):
public class CreateNewsViewModel
{
[Required]
public string Subject { get; set; }
[Required]
public string Content { get; set; }
public SelectList Categories { get; set; }
public SelectList SelectedCategories { get; set; }
public int Category { get; set; }
}
after that, you should change code to filling model.Category to this:
model.Categories = new SelectList(categories, "Id", "Name", model.Category);
and than, in your view, editor for Category should look like this:
<div class="editor-field">
#Html.DropDownListFor(m => m.Category, Model.Categories);
</div>
model.Categories = new SelectList(_db.NewsCategories, "Id", "Name");
At this line you fill Categories property
#Html.DropDownListFor(model => model.Categories, Model.SelectedCategories)
Here you trying create select with Categories name and SelectedCategories items
int id = int.Parse(newsModel.SelectedCategories.Single(f => f.Selected).Value);
Here you trying get selected item, BUT on form submit you only sending one number, not list. One of your SelectList must be int
I found the solution.
I had to change the ViewModel to this:
public IEnumerable<SelectListItem> Categories { get; set; }
public int SelectedCategoryId { get; set; }
I changed the controller to:
var model = new CreateNewsViewModel
{
Categories = new SelectList(_db.NewsCategories, "Id", "Name")
};
And changed View to this:
#Html.DropDownListFor(x => x.SelectedCategoryId, Model.Categories)