Form not saving data to model - c#

I am using MVC in order to build a blog. What I want is to save post comments to its corresponding place in the database but it does not work.
My post model is as follows:
public class Post
{
[Key]
public int PostId { get; set; }
public string Title { get; set; }
public DateTime CreatedDate{get;set;}
public DateTime UpdateDate { get; set; }
public string Body { get; set; }
public ICollection<Comment> Comments { get; set;}
public ICollection<Tag> Tags { get; set; }
}
My Comment model is as follows:
public class Comment
{
[Key]
public int CommentId { get; set; }
public int PostId { get; set; }
[ForeignKey("PostId")]
public virtual Post Post{get; set;}
public string CommentCreateDate { get; set; }
public string CommentUpdateDate { get; set; }
public string CommeterName { get; set; }
public string EmailAddress { get; set; }
public string CommentText { get; set; }
public bool Approved { get; set; }
}
I have the following Action Methods:
[HttpGet]
public ActionResult CreateComment()
{
return View();
}
[HttpPost]
[ValidateInput(false)]
public ActionResult CreateComment(int id, string name, string email, string txt, bool aproved = false)
{
Post post = GetPost(id);
Comment comment = new Comment();
comment.Post = post;
comment.CommentCreateDate = DateTime.Now.ToString();
comment.CommeterName = name;
comment.EmailAddress = email;
comment.CommentText = txt;
comment.Approved = aproved;
db.Comments.Add(comment);
db.SaveChanges();
m_commentList.Add(comment);
return RedirectToAction("CreateComment", new { id = id });
}
And in my view I am trying this:
#model Blog.Models.Comment
#{
ViewBag.Title = "CreateComment";
}
<h2>Create a Comment</h2>
#using (Html.BeginForm())
{
<fieldset>
<legend>Enter Comment</legend>
<div class="editor-label">
#Html.LabelFor(model => model.CommeterName)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.CommeterName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.EmailAddress)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.EmailAddress)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.CommentText)
</div>
<div class="editor-field">
#Html.TextAreaFor(model => model.CommentText)
</div>
<p>
<input type="submit" value="Create comment" />
</p>
</fieldset>
}
I get no exception but none of the data from the form is being saved. Only the data that is set in the action result, that is, CommentCreateDate and Approved. I am not sure what I am doing wrong.
I have tried a second option which is to include the id of the comment in BeginForm() as follows:
#using (Html.BeginForm(new {id = Model.CommentId}))
{
<fieldset>
<legend>Enter Comment</legend>
<div class="editor-label">
#Html.LabelFor(model => model.CommeterName)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.CommeterName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.EmailAddress)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.EmailAddress)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.CommentText)
</div>
<div class="editor-field">
#Html.TextAreaFor(model => model.CommentText)
</div>
<p>
<input type="submit" value="Create comment" />
</p>
</fieldset>
}
This will give me a null reference exception even if I use the new keyword :
System.NullReferenceException: Object reference not set to an instance of an object.
Why is this happening? Can anybody help?
Thank you

Your action signature should be:
public ActionResult CreateComment(Comment model)
The names generated for the form fields will bind back to the properties of the same model class. There's no way for the framework to know, for example, that the CommenterName property should match up to the name parameter of the action.
Your second example makes very little sense - you're trying to write out the ID but you have never set one. In fact, you don't even pass a Comment to the view with the form, which is why you get a NullReferenceException:
[HttpGet]
public ActionResult CreateComment()
{
return View();
}
Also, you should be careful with what fields you expose to your models and actions. For example, a user could easily force their comment to be approved just by adding the following markup via their browser's development console:
<input type="hidden" name="approved" value="true" />
Anything that is either in your model properties or a parameter to your action can be set by the user.
An altogether better option would be to use a dedicated model class for the form:
public class CreateCommentViewModel
{
public string Name { get; set; }
public string Email { get; set; }
public string Text { get; set; }
}
Then to map this to your Comment in your action with:
[HttpPost]
public ActionResult CreateComment(CommentViewModel model)
{
var comment = new Comment();
comment.CommenterName = model.Name;
// etc...
}
This prevents the user from being able to set things like Approved and CreatedDate.

Related

How to save changes to a view model in C#?

I am currently reading values in a configuration file and setting the values to a view model. I am displaying them on the UI in textboxes. I want the user to be able to edit/change the value in the textbox and be able to hit a save button and for the changes to be saved to the view model and the configuration file. I understand there needs to be some type of Get/Post method in the controller but I'm not entirely sure how the controller should look. I am not connecting it to a database at all.
View:
#using (Html.BeginForm())
{
<fieldset>
<div class="row">
<div class="col-md-1">Logging Directory:</div>
<div class="col-md-2">#Html.EditorFor(model => Model.loggingDirectory)</div>
<div class="col-md-1">Archive Directory:</div>
<div class="col-md-2">#Html.EditorFor(model => Model.archiveDirectory)</div>
<div class="col-md-1">Time Between Alarms:</div>
<div class="col-md-2">#Html.EditorFor(model => Model.timeBetweenAlarms)</div>
<div class="col-md-1">Time to Archive Logs:</div>
<div class="col-md-2">#Html.EditorFor(model => Model.timeToArchive)</div>
<div class="col-md-1">Situator IP:</div>
<div class="col-md-2">#Html.EditorFor(model => Model.situatorIP)</div>
<div class="col-md-1 ">Situator Port:</div>
<div class="col-md-2 ">#Html.EditorFor(model => Model.situatorPort)</div>
<div class="col-md-1 ">Clean Up:</div>
<div class="col-md-2 ">#Html.EditorFor(model => Model.timeToCleanUp)</div>
<div class="col-md-1 ">Coorelation Zone:</div>
<div class="col-md-2">#Html.EditorFor(model => Model.coorelationZone)</div>
</div>
<div class="row submitButton">
<button class="btn btn-primary" type="submit">Save</button>
</div>
</fieldset>
}
View Model
public class ConfigurationViewModel
{
public string loggingDirectory { get; set; }
public string archiveDirectory { get; set; }
public string situatorIP { get; set; }
public string situatorPort { get; set; }
public string timeBetweenAlarms { get; set; }
public string timeToArchive { get; set; }
public string sightlogixIP { get; set; }
public string timeToCleanUp { get; set; }
public string coorelationZone { get; set; }
}
Controller:
public ActionResult Index()
{
ConfigurationViewModel cvm = new ConfigurationViewModel();
cvm.loggingDirectory = ConfigurationManager.AppSettings["loggingDirectoryPath"];
cvm.archiveDirectory = ConfigurationManager.AppSettings["archiveDirectoryPath"];
cvm.situatorIP = ConfigurationManager.AppSettings["SituatorIP"];
cvm.situatorPort = ConfigurationManager.AppSettings["SituatorPort"];
cvm.timeBetweenAlarms = ConfigurationManager.AppSettings["TimeIncrementBetweenalarmsInSeconds"];
cvm.timeToArchive = ConfigurationManager.AppSettings["timeIncrementForArchivingLogFilesInHours"];
cvm.sightlogixIP = ConfigurationManager.AppSettings["SightLogixIP"];
cvm.timeToCleanUp = ConfigurationManager.AppSettings["timeIncrementForCleaningUp"];
cvm.coorelationZone = ConfigurationManager.AppSettings["correlationZoneLengthInFeet"];
return View(cvm);
}
[HttpGet]
public ActionResult Edit()
{
return;
}
[HttpPost]
public ActionResult Edit()
{
return;
}
Pass view model in Get Edit method
[HttpGet]
public ActionResult Edit()
{
ConfigurationViewModel cvm = new ConfigurationViewModel();
cvm.loggingDirectory = ConfigurationManager.AppSettings["loggingDirectoryPath"];
cvm.archiveDirectory = ConfigurationManager.AppSettings["archiveDirectoryPath"];
cvm.situatorIP = ConfigurationManager.AppSettings["SituatorIP"];
cvm.situatorPort = ConfigurationManager.AppSettings["SituatorPort"];
//...
return View(cvm);
}
Send updated view model to post edit method and perform action on it
[HttpPost]
public ActionResult Edit(ConfigurationViewModel cvm)
{
ConfigurationManager.AppSettings["archiveDirectoryPath"] = cvm.archiveDirectory;
ConfigurationManager.AppSettings["SituatorIP"] = cvm.situatorIP;
ConfigurationManager.AppSettings["SituatorPort"]= cvm.situatorPort;
//...
return View(cvm);
}
And your razor view which will submit updated data to your Post Edit method
#using (Html.BeginForm("Edit", "Your controller", FormMethod.Post))
{
....
}

Entry saved to two tables has one to many relationship in mvc 4 code first

I simplified my problem as i could ,I have two classes Employee ,Departments
I supposed to make a one to many relationship between them
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public string salary { get; set; }
public virtual Department depatments { get; set; }
}
//////////////////////
public class Department
{
public Department()
{
this.employees = new HashSet<Employee>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Employee> employees { get; set; }
}
and that is my context class
public class myEntity :DbContext
{
public DbSet<Employee> employees {get;set;}
public DbSet<Department> Departments {get;set;}
}
well, I have created a Create ActionResult in order to be able to add new record to Employee and filled a selectlist with Department table/Class
And sent it to view by viewbag
[HttpGet]
public ActionResult Create()
{
ViewBag.xx = new SelectList(mycontext.Departments, "Id", "Name");
return View();
}
[HttpPost]
public ActionResult Create(Employee emp)
{
mycontext.employees.Add(emp);
mycontext.SaveChanges();
return RedirectToAction("Index", "Employee");
}
And there is the view
**#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Employee</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.salary)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.salary)
#Html.ValidationMessageFor(model => model.salary)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.depatments)
</div>
<div class="editor-field">
#Html.DropDownListFor(model =>model.depatments.Id,#ViewBag.xx as SelectList)
#Html.ValidationMessageFor(model => model.salary)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
I ran it, every thing is ok and database created.
the view dropdownlist render ID And Name as it should.
Normally ,Every Employee record should have Department ID as a Foreign Key.
The problem is When i add new record to Employee it add a new record in Department where it should pick just ID and add it to Employee
Note: I filled the Department first With no problem
If you use a different context to save then it should work without this issue. Your Departments entity is still being tracked in the first context and this is the reason for the issue.

textarea with writing code

I suffer from the following there
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Posts</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Title)
#Html.ValidationMessageFor(model => model.Title)
</div>
<div class="editor-label">
<h4>Теги:</h4>
</div>
<div class="editor-field">
<input type="text" name="tags" value="#ViewBag.Tags"/><br />
</div>
#Html.HiddenFor(model => model.DateTime, new { #Value = System.DateTime.Now.ToString("dd-MM-yyyy HH:mm:ss") })
#Html.ValidationMessageFor(model => model.DateTime)
<h5>body:</h5><textarea name="body" rows="10" cols="80" required >
</textarea><br />
<div class="editor-label">
#Html.LabelFor(model => model.Avtor)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Avtor)
#Html.ValidationMessageFor(model => model.Avtor)
</div>
<p>
<input type="submit" value="save" />
</p>
</fieldset>
}
in
public ActionResult Create([Bind(Include = "Id,Title,DateTime,Body,Avtor")] Posts post, string tags, string body)
{
if (ModelState.IsValid)
{
tags = tags ?? string.Empty;
string[] tagNames = tags.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string tagName in tagNames)
{
post.Tags.Add(GetTag(tagName));
}
post.Body = body;
db.Posts.Add(post);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(post);
}
public partial class Posts
{
public Posts()
{
this.Comments = new HashSet<Comments>();
this.Tags = new HashSet<Tags>();
}
public int Id { get; set; }
[Required]
[StringLength(64, MinimumLength = 1)]
public string Title { get; set; }
public System.DateTime DateTime { get; set; }
[Required]
[AllowHtml]
public string Body { get; set; }
[Required]
[StringLength(64, MinimumLength = 1)]
public string Avtor { get; set; }
public virtual ICollection<Comments> Comments { get; set; }
public virtual ICollection<Tags> Tags { get; set; }
}
if the user dials in plain text in textarea, it's okay, but there is a need to add the code text. It makes <pre> and <code>but it can only do I have in the program, not the user in textarea. Strictly speaking how to do so in tehtarea could write code and save it in the database and not get an error using <a> <script> and that they could be seen in the text. so the spacecraft itself is implemented on this site. Thanks in advance.
Create a strongly typed model to accept the data posted to the server, use the data annotation AllowHtml to accept HTML as input:
public class SomeViewModel
{
Posts Post { get; set; }
[AllowHtml]
public string Body { get; set; }
}
Then change the controller to
public ActionResult Create(SomeViewModel model)
{
if (ModelState.IsValid)
{
model.Post.Body = model.Body;
db.Posts.Add(model.Post);
db.SaveChanges();
return RedirectToAction("Index");
}
return RedirectToAction("Index");
}
Also try adding this to the following location in your web.config file just above the pages section:
<system.web>
...
<httpRuntime requestValidationMode="2.0" enableVersionHeader="false" />
<pages...
...
</system.web>
Changing the request validation mode will essentially only validation pages rather than every http request. You can read more about it here on MSDN HttpRuntimeSection.RequestValidationMode
Use AllowHTMLAttribute
see this MSDN
This will only allow html for that specific field in your viewmodel but not the whole thing.
or alternatively you can do:
[ValidateInput(false)]
public ActionResult someAction(SomeViewModel model)
{
//Your code to handle the control
}
EDIT: Notice that your textarea is not part of the model in your HTML, can you change that <textarea name="body".... line to:
#Html.TextArea("body", new { rows=10, columns=80 })
After that your body textarea element should be able to capture HTML elements to your controller.

The data has been cleared after post

I have edit page and controller that creates new model object and fills some data from db into this object then send a model object to view. When I click the submit button, some fields in this object have been cleared.
For example:
Before:
user_id
name
birth_date
username
password
id_role
email
After (Fields that are not null or empty):
name
username
birth_date
The model:
public partial class Users
{
public Users()
{
this.Albums = new HashSet<Albums>();
this.History = new HashSet<History>();
this.Country = new HashSet<Country>();
this.Artists = new HashSet<Artists>();
this.SelectedCountries = new List<string>();
}
[DisplayName("User ID")]
public System.Guid user_id { get; set; }
[DisplayName("Name")]
public string name { get; set; }
[DisplayName("Birth date")]
public Nullable<System.DateTime> birth_date { get; set; }
[DisplayName("Username")]
public string username { get; set; }
[DisplayName("Password")]
public string password { get; set; }
[DisplayName("Rights")]
public System.Guid id_role { get; set; }
[DisplayName("User Email")]
public string email { get; set; }
public bool isRemember { get; set; }
public virtual ICollection<Albums> Albums { get; set; }
public virtual ICollection<History> History { get; set; }
public virtual Role Role { get; set; }
public virtual ICollection<Country> Country { get; set; }
public virtual ICollection<Artists> Artists { get; set; }
public virtual List<string> SelectedCountries { get; set; }
}
Edit method:
public ActionResult Edit()
{
if (HttpContext.User.Identity.IsAuthenticated)
{
var userName = HttpContext.User.Identity.Name;
var user = db.Users.Where(x => x.username == userName).FirstOrDefault();
ViewBag.Countries = new MultiSelectList(db.Country, "id_country", "name", user.SelectedCountries);
return View(user);
}
return HttpNotFound();
}
Edit method for handling post request:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Users users)
{
if (ModelState.IsValid)
{
foreach (var country in users.SelectedCountries)
{
var dbCountry = db.Country.Find(new Guid(country));
if (dbCountry != null)
users.Country.Add(dbCountry);
}
db.Entry(users).State = System.Data.Entity.EntityState.Modified;
//There handle of string array goes
db.SaveChanges();
return RedirectToAction("Index");
}
return View(users);
}
View:
<h2>Edit</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Users</legend>
<div class="editor-label">
#Html.LabelFor(model => model.name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.name)
#Html.ValidationMessageFor(model => model.name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.birth_date)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.birth_date)
#Html.ValidationMessageFor(model => model.birth_date)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.username)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.username)
#Html.ValidationMessageFor(model => model.username)
</div>
<div class="editor-label">
#Html.Label("Country")
</div>
<div class="editor-field">
#Html.DropDownList("SelectedCountries", (ViewBag.Countries as MultiSelectList), new { multiple = "multiple", #class = "chosen", style = "width: 350px;"})
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
Thanks in advance :)
You will only receive values that are in your form. Http is stateless..
What you need to do.. is create a ViewModel. That ViewModel is the subset of properties from your domain model that are displayed in the view. Like this:
public class UserViewModel {
public string Name { get; set; }
public string Username { get; set; }
public DateTime? DateofBirth { get; set; }
}
Use this model in your view. Then, in your controller.. get the user and update the appropriate fields:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(UserViewModel viewModel) {
var user = db.Users.Where(x => x.username == viewModel.Username).FirstOrDefault();
user.Name = viewModel.Name;
user.Username = viewModel.Username;
// .. etc.
db.SaveChanges();
}
If you are worried about all of the manual mapping involved in this, there exists frameworks to help you with that:
Automapper
ValueInjector
You are heading down a very very daunting path if you start adding hidden fields into your view. Its a maintenance nightmare and very error prone.
The post operation only collects the values you have in the form.
If you want the other values to proceed in your controllers post-method, you can for example, add hidden fields.
#Html.HiddenFor(x => x.HiddenPostBack)

ModelState.IsValid is false when binded to dropdownlist

Model:
faculty class
[Key]
[Required(ErrorMessage="*Enter Faculty id")]
[StringLength(5)]
public string Id { get; set; }
[Required(ErrorMessage="*Enter First Name")]
[MaxLength(30)]
public string F_Name { get; set; }
[Required(ErrorMessage="*Enter Last Name")]
[MaxLength(30)]
public string L_Name { get; set; }
[MaxLength(30)]
public string M_Name { get; set; }
[Required(ErrorMessage="*Enter Email id")]
[MaxLength(50)]
public string Email{ get; set; }
[Required(ErrorMessage="*Enter Department")]
public Int16 Dept_Id { get; set; }
[ForeignKey("Dept_Id")]
public virtual Department Dept { get; set; }
Department class:
public class Department //static table
{
[Key]
public Int16 Id { get; set; }
[Required]
[MaxLength(20)]
public string Dept_Name { get; set; }
}
View:
<div class="editor-label">
Faculty Id
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.Id, new { style = "width:200px;"})
#Html.ValidationMessageFor(model => model.Id,null, new { style="color:Red"})
</div>
<div class="editor-label">
First Name
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.F_Name,new { style = "width:200px;"})
#Html.ValidationMessageFor(model => model.F_Name, null, new { style="color:red;"})
</div>
<div class="editor-label">
Middle Name
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.M_Name,new { style = "width:200px;"})
#Html.ValidationMessageFor(model => model.M_Name)
</div>
<div class="editor-label">
Last Name
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.L_Name,new { style = "width:200px;"})
#Html.ValidationMessageFor(model => model.L_Name, null, new { style="color:red;"})
</div>
<div class="editor-label">
Department
</div>
<div class="editor-field">
#Html.DropDownListFor(model =>model.Dept_Id ,ViewBag.Dept_Id as IEnumerable<SelectListItem>,
string.Empty,new { style = "width:200px;text-align:center;"})
#Html.ValidationMessageFor(model => model.Dept_Id, null, new { style="color:red;"})
</div>
<p>
<input type="submit" value="Create" />
</p>
Controller:
[HttpPost]
public ActionResult Create(Faculty faculty)
{
faculty.Email = faculty.F_Name + "." + faculty.L_Name + "#mitcoe.edu.in";
if (ModelState.IsValid)
{
db.Faculty.Add(faculty);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.Dept_Id = new SelectList(db.Department, "Id", "Dept_Name", faculty.Dept_Id);
return View(faculty);
}
The drop down list works fine and the ID for all the fields is fetched properly from the view.
But in the post function the modelstate is invalid as the Dept_name is null.
ORM creates the tables with only Dept_Id as a foreign key.
Why is the controller expecting Dept_Name also? Is anything wrong with my models?
The controller is expecting Dept_Name because:
You are model binding to the Faculty class.
Department is a component of Faculty.
Dept_Name is a required field on Department.
Dept_Name is null when the model binding parses the form data,
as you do not have it as an input anywhere in you view.
Two suggestions, either:
Have a separate FacultyInputModel class which is
the parameter to the Create action method. FacultyInputModel includes only those properties which you expect to be returned from the form;
Or: Use HiddenFor with the Dept_Name property, so it is included in your form data posted from you view and the model state is valid.
I'd recommend the first personally. It can be useful sometimes to separate your view models, i.e. what data you are displaying, from your input models, i.e. what data you expect to be posted back. It does add more classes and complexity though, on the down side. See e.g. https://www.simple-talk.com/dotnet/asp.net/the-three-models-of-asp.net-mvc-apps/ for a bit about input models.

Categories

Resources