Cannot resolve ASP.NET MVC error - c#

I'm a new learner of ASP.NET MVC. This problem should be easy to answer for professions. I'm trying to submit a form (post comment), and getting error.
Here is my controller
[HttpPost]
public ActionResult Index(long id, CommentViewModel comment) {
var service = new UserService();
var articleService = new ArticleService();
comment.UserId = service.GetUserByUsername(User.Identity.Name).UserId;
comment.ArticleId = id;
comment.CommentTime = DateTime.Now;
articleService.AddArticleComment(comment);
return View();
}
here is my ViewModel
public class CommentViewModel : BaseViewModel {
public Int64 CommentId { get; set; }
public Int64 UserId { get; set; }
public Int64 ArticleId { get; set; }
public DateTime CommentTime { get; set; }
public String CommentBody { get; set; }
}
here is my View (HTML)
<form class="form-stacked" id="comment-form" action="#Url.Action("Index", "Article")" method="post" enctype="multipart/form-data">
<label class="control-label" for="commentTextArea">Leave a comment...</label>
<textarea rows="3" id="commentTextArea" name="commentBody" class="span8"></textarea>
<input type="submit" value="Post Comment" />
</form>
this is URL
http://localhost:58856/Article?Id=1
and this is the Error Message
The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int64' for method 'System.Web.Mvc.ActionResult Index(Int64, TechCells.Services.ViewModels.CommentViewModel)' in 'TechCells.Web.UI.Controllers.ArticleController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
Parameter name: parameters
I guess MVC should automatically detect the id from the URL, and automatically assign ViewModel properties based on names. Right?
What is the reason for this error? How can I fix?
Thanks.

Your form doesn't have an <input name="id" /> (which is necessary per your action signature). Remember that MVC's action arguments are incoming values, so if this is a form submit, all those parameters need to be supplied (though sometimes they can be defaulted in a route definition, but I won't get in to that).
Mis-read and missed the Id coming from a GET parameter. Assuming you follow traditional form handling with a GET & POST action, you're passing the Id to the GET, it renders a view, then on POST the Id's dropped. So, two options:
If you don't need it, remove the long id parameter or make it optional (line id = 0) so that MVC can carry on with a default value.
If you do need it, make sure to pass it off in your Url.Action() as a routeValue so it's received back in.
Also, don't be afraid to use the HTML Helpers, like Html.BeginForm, Html.LabelFor, Html.TextBoxFor, etc.
#using (Html.BeginForm("Index", "Article",
new { id = __ID_FROM_GET_REQUEST_ }, // to hand-off to POST action
FormMethod.Post,
new { enctype = "multipart/form-data" }
))
{
#* I assume these are here to help reference what the comment is regarding *#
#Html.HiddenFor(x => x.UserId)
#Html.HiddenFor(x => x.ArticleId)
#* are these ones auto-generated at creation time? if so, remove these. I
don't know enough about your work-flow, so just going to place them for
now. I also don't know what your validation requirements are. *#
#Html.HiddenFor(x => x.CommentId)
#Html.HiddenFor(x => x.CommentTime)
#* Now we get in to user-interaction *#
#Html.LabelFor(x => x.CommentBody)
#Html.TextAreaFor(x => x.CommentBody, new { #class = "span8", rows = "3" })
<input type="submit" value="Post Comment" />
}
Then for LabelFor to work, make sure you decorate your model:
/* ...snip ... **/
[Display(Name = "Leave a comment...")]
public String CommentBody { get; set; }
/* ...snip ... **/

Changing it to this
public ActionResult Index(long id = 0, CommentViewModel comment)
OR
this
public ActionResult Index(long? id, CommentViewModel comment)
will solve the problem.
The error is occuring because you are not passing any value for id. and id is not nullable.
To solve this, either pass id or change the method signature as above.

Related

Integer property without Required property is still required

I'm not applying any [Required] attribute validation on the integer property. But every time I post the form it fired Validation. Initially it was using HTML 5 data attribute on client side. I've set ClientValidationEnabled to false in Web.config. After that it is firing Required attribute validation.
I've created a new project but same situation. Tried to change .Net framework from 4.6 to 4.5 but no success. Also tried VS2015 and VS 2013
Client side razor code
#using (Html.BeginForm("Index", "Home", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.LabelFor(model => model.Weight)
#Html.TextBoxFor(model => model.Weight, new { #class = "form-control", #placeholder = "Weight", Value = "" })
#Html.ValidationMessageFor(model => model.Weight, "", new { #class = "text-danger" })
<input type="submit" class="btn btn-default" value="Save" />
}
Model:
public class RegistrationModel
{
public int Weight { get; set; }
}
Action Method
public ActionResult Index()
{
var model = new RegistrationModel();
return View(model);
}
[HttpPost]
public ActionResult Index(RegistrationModel model)
{
if (ModelState.IsValid)
{
}
return View();
}
Thanks
Your model property is typeof int. An int must always have a value (its not nullable) so irrespective of disabling client side validation, as soon as you hit the controller, ModelState will be invalid if you submit a null (empty string) value for Weight.
If you want to allow nulls, then you must make the property nullable
public int? Weight { get; set; }
Side note: You should never attempt to set the value attribute when you are using the html helpers to bind to your model properties.
This is default behavior of integer type property because int has to
have a value even zero.So, you have to make it of type nullable int in the model.
this might help you,
<form method="post" action="/foo" novalidate>...</form>
or
<form>
<input type="email"/> <!-- Will be validated -->
<input type="string" formnovalidate/> <!-- Will not be validated -->
</form>

ModelView data not passed from View to Controller

My problem is that strongly typed data passed from my Controller to my view comes out empty (all it's properties are null).
I would also like to bind selected value in radiobuttons (marked as QUESTION2) to model property "GivenAnwser" but it doesn't seem to work either.
Type passed around is a ViewModel
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
QuestionViewModel question = Manager.GetQuestion();
return View(question);
}
[HttpPost]
public ActionResult Index(QuestionViewModel question,Anwser givenAnwser)
{
//QuestionViewModel is returned but all it's properties are null.
return View(question);
}
}
VIEW
#model Quiz.ViewModels.QuestionViewModel
#{
ViewBag.Title = "Home Page";
}
#Html.Label("Question:")
#if (Model.CorrectAnwser != null)
{
//some code
}
#Html.DisplayFor(model => model.Question.Text)
//I have tried with Hidden fields and without them
#Html.HiddenFor(model => model.Question)
#Html.HiddenFor(model => model.Anwsers)
#using (Html.BeginForm("Index", "Home"))
{
foreach (var anwser in Model.Anwsers)
{
//QUESTION 2
<input type="radio" name="givenAnwser" value="#anwser" />
<label>#anwser.Text</label>
<br />
}
<input type="submit" value="Check!" />
}
QuestionViewModel
public class QuestionViewModel
{
public QuestionViewModel()
{
this.Anwsers = new List<Anwser>();
}
public Question Question { get; set; }
public List<Anwser> Anwsers { get; set; }
public Anwser GivenAnwser { get; set; }
public bool CorrectAnwser { get; set; }
}
EDIT:
ModelState contains an error:
"The parameter conversion from type 'System.String' to type 'Quiz.Models.Anwser' failed because no type converter can convert between these types."
<input type="radio" name="givenAnwser" value="#anwser" />
this line is setting a complex type "#answer" as the value of the radio button.
this might just do a ToString() of the type during rendering.
When you post it back, MVC might be trying to convert this string value back to
Quiz.Models.Anwser
and failing.
you should probably render
<input type="radio" name="givenAnwser" value="#anwser.SomeBooleanValue" />
p.s. also, why not use the Html Extension to render the radio button.
You can't bind a complex type (Question) to a Hidden field. You would need to bind to the separate child properties.
Also, for the answers, don't use a foreach, use a for loop. Like:
#for(var i=0;i<Answers.Count;i++)
{
<input type="radio" name="#Html.NameFor(a=>a.Answers[i].answer.Value)" value="#Model.Answers[i].anwser.Value" />
}
or
#for(var i=0;i<Answers.Count;i++)
{
#Html.RadioButtonFor(a=>a.Answers[i].answer,Model.Answers[i].answer.Value)
}
Although, that may not be correct either because Answers is a collection of a complex type as well, and you didn't share it's definition.
All in all, I don't think you really need (or want) to post back the entire Model back anyhow. Why not just post back the question ID, and the selected Answer?

Passing value for required property at Controller, but still ModelState.IsValid is false

I have a partial view to post comment for my article module on my main view for article detail. Model for comment has three required fields, ID (identity field), ArticleId and CommentText. (I am using Razor syntax)
I tried to pass ArticleId at controller in Create Action.
public ActionResult Create(ArticleComment articlecomment, string AID)
{
articlecomment.ArticleId = AID; //this is required
if (User.Identity.IsAuthenticated)
{
articlecomment.UserId = WebSecurity.CurrentUserId.ToString();
}
else
{
articlecomment.UserId = Constants.Anonymus;
}
articlecomment.CommentDate = DateTime.Now;
if (ModelState.IsValid)
{
db.ArticleComment.Add(articlecomment);
int success = db.SaveChanges();
if (success > 0)
{
return Content("<script language='javascript' type='text/javascript'>alert('Comment added successfully.');window.location.href='" + articlecomment.ArticleId + "';</script>");
}
else
{
return Content("<script language='javascript' type='text/javascript'>alert('Posting comment has failed, please try later.');window.location.href='" + articlecomment.ArticleId+ "';</script>");
}
}
return PartialView(articlecomment);
}
But still ModelState.IsValid is returning false. I have used following code and find that ModelState is getting ArticleId as null.
foreach (var modelStateValue in ViewData.ModelState.Values)
{
foreach (var error in modelStateValue.Errors)
{
// Do something useful with these properties
var errorMessage = error.ErrorMessage;
var exception = error.Exception;
}
}
I have also thought to set value for ArticleId using Hidden field using ViewBag but have not find any working code. I tried following:
#Html.HiddenFor(model => model.ArticleId, new { #value = ViewBag.Article })
and
#Html.HiddenFor(model => model.ArticleId, (object)ViewBag.Article)
My 'ParticalView' to post comment is:
#model Outliner.Models.ArticleComment
<script src="~/Scripts/jquery-1.8.2.min.js"></script>
<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<div class="editor-label">
#* #Html.HiddenFor(model => model.ArticleId, new { #value = ViewBag.Article })
#Html.HiddenFor(model => model.ArticleId, (object)ViewBag.Article)*#
#Html.LabelFor(model => model.Comment)
<span class="error">#Html.ValidationMessageFor(model => model.Comment)</span>
</div>
#Html.TextAreaFor(model => model.Comment)
<input type="submit" value="Post" />
}
And this is how I am calling this partial view on 'ArticalDetail' view (my main view):
#Html.Action("Create", "ArticleComment")
I have passed required field value at controller for a View before, but I am facing issue for PartialView. What I am doing wrong and how can I make this work?
Edit After a try
As Satpal and Fals lead me to a direction, I tried their suggestions, and tried following:
TryUpdateModel(articlecomment);
and also
TryUpdateModel<ArticleComment>(articlecomment);
and also
TryValidateModel(articlecomment);
but I was still getting same validation error for ArticleId, then I checked in Watch and all tree methods I tried are returning False.
I also tried following:
UpdateModel(articlecomment);
and
UpdateModel<ArticleComment>(articlecomment);
above methods are generating an exception :
The model of type 'Outliner.Models.ArticleComment' could not be
updated.
Here is my model:
[Table("ArticleComments")]
public class ArticleComment
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public string ArticleId { get; set; }
public string UserId { get; set; }
[Required]
[Display(Name = "Comment")]
public string Comment { get; set; }
[Required]
[Display(Name = "Commented On")]
public DateTime CommentDate { get; set; }
}
I don't get it, why my model is not updating ... :(
It is quite late answer, but it should work.
Before ModelState.IsValid, add following
ModelState.Remove("ArticleId");
It will remove that field from validation.
You can try TryUpdateModel(articlecomment) once before checking ModelState.IsValid. However I have not tested it
After update any requerid field after the ModelBind you must call another method to update the validation.
You can use:
TryValidateModel(articlecomment);
or
TryUpdateModel<ArticleComment>(articlecomment);
To me it seems that your #Html.Action(...) code it invoking the action to create the partial view, like you said. If you are doing this it isn't the correct way to invoke a partial view. While it isn't uncommon for a action to return a partial view, it is normally via AJAX, in my experience, so you can just insert it into the DOM after it returns.
You can use the following method to render a partial view:
#{
Html.RenderPartial("_myPartialView",
new ArticleComment {ArticleId = model.Id});
}
This should render your partial view, pass your model to it so it can render properly. Then when the form is POST'ed to the server it should create the model from the form data. You shouldn't need the AID parameter as it is part of your ArticleComment model.

MVC 4 ViewModel not being sent back to Controller

I can't seem to figure out how to send back the entire ViewModel to the controller to the 'Validate and Save' function.
Here is my controller:
[HttpPost]
public ActionResult Send(BitcoinTransactionViewModel transaction)
{
}
Here is the form in the view:
<li class="check">
<h3>Transaction Id</h3>
<p>#Html.DisplayFor(m => m.Transaction.TransactionId)</p>
</li>
<li class="money">
<h3>Deposited Amount</h3>
<p>#Model.Transaction.Amount.ToString() BTC</p>
</li>
<li class="time">
<h3>Time</h3>
<p>#Model.Transaction.Time.ToString()</p>
</li>
#using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post, new { transaction = Model }))
{
#Html.HiddenFor(m => m.Token);
#Html.HiddenFor(m => m.Transaction.TransactionId);
#Html.TextBoxFor(m => m.WalletAddress, new { placeholder = "Wallet Address", maxlength = "34" })
<input type="submit" value="Send" />
#Html.ValidationMessage("walletAddress", new { #class = "validation" })
}
When i click on submit, the conroller contains the correct value of the walletAddress field but transaction.Transaction.Time, transaction.Transaction.Location, transaction.Transaction.TransactionId are empty.
Is there a way i could pass the entire Model back to the controller?
Edit:
When i dont even receive the walletAddress in the controller. Everything gets nulled!
When i remove this line alone: #Html.HiddenFor(m => m.Transaction.TransactionId);
it works and i get the Token property on the controller, but when i add it back, all the properties of the transaction object on the controller are NULL.
Here is the BitcoinTransactionViewModel:
public class BitcoinTransactionViewModel
{
public string Token { get; set; }
public string WalletAddress { get; set; }
public BitcoinTransaction Transaction { get; set; }
}
public class BitcoinTransaction
{
public int Id { get; set; }
public BitcoinTransactionStatusTypes Status { get; set; }
public int TransactionId { get; set; }
public decimal Amount { get; set; }
public DateTime Time { get; set; }
public string Location { get; set; }
}
Any ideas?
EDIT: I figured it out, its in the marked answer below...
OK, I've been working on something else and bumpend into the same issue all over again.
Only this time I figured out how to make it work!
Here's the answer for anyone who might be interested:
Apparently, there is a naming convention. Pay attention:
This doesn't work:
// Controller
[HttpPost]
public ActionResult Send(BitcoinTransactionViewModel transaction)
{
}
// View
#using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post, new { transaction = Model }))
{
#Html.HiddenFor(m => m.Token);
#Html.HiddenFor(m => m.Transaction.TransactionId);
.
.
This works:
// Controller
[HttpPost]
public ActionResult Send(BitcoinTransactionViewModel **RedeemTransaction**)
{
}
// View
#using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post, new { **RedeemTransaction** = Model }))
{
#Html.HiddenFor(m => m.Token);
#Html.HiddenFor(m => m.Transaction.TransactionId);
.
.
In other words - a naming convention error! There was a naming ambiguity between the Model.Transaction property and my transaction form field + controller parameter. Unvelievable.
If you're experiencing the same problems make sure that your controller parameter name is unique - try renaming it to MyTestParameter or something like this...
In addition, if you want to send form values to the controller, you'll need to include them as hidden fields, and you're good to go.
The signature of the Send method that the form is posting to has a parameter named transaction, which seems to be confusing the model binder. Change the name of the parameter to be something not matching the name of a property on your model:
[HttpPost]
public ActionResult Send(BitcoinTransactionViewModel model)
{
}
Also, remove the htmlAttributes parameter from your BeginForm call, since that's not doing anything useful. It becomes:
#using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post))
Any data coming back from the client could have been tampered with, so you should only post back the unique ID of the transaction and then retrieve any additional information about it from your data source to perform further processing. You'll also want to verify here that the user posting the data has access to the specified transaction ID since that could've been tampered with as well.
This isn't MVC specific. The HTML form will only post values contained within form elements inside the form. Your example is neither inside the form or in a form element (such as hidden inputs). You have to do this since MVC doesn't rely on View State. Put hidden fields inside the form:
#Html.HiddenFor(x => x.Transaction.Time)
// etc...
Ask yourself though.. if the user isn't updating these values.. does your action method require them?
Model binding hydrates your view model in your controller action via posted form values. I don't see any form controls for your aforementioned variables, so nothing would get posted back. Can you see if you have any joy with this?
#using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post, new { transaction = Model }))
{
#Html.TextBoxFor(m => m.WalletAddress, new { placeholder = "Wallet Address", maxlength = "34" })
#Html.Hidden("Time", Model.Transaction.Time)
#Html.Hidden("Location", Model.Transaction.Location)
#Html.Hidden("TransactionId", Model.Transaction.TransactionId)
<input type="submit" value="Send" />
#Html.ValidationMessage("walletAddress", new { #class = "validation" })
}
Try to loop with the folowing statement not with FOREACH
<table>
#for (var i = 0; i < Model.itemlist.Count; i++)
{
<tr>
<td>
#Html.HiddenFor(x => x.itemlist[i].Id)
#Html.HiddenFor(x => x.itemlist[i].Name)
#Html.DisplayFor(x => x.itemlist[i].Name)
</td>
</tr>
}
</table>
Try Form Collections and get the value as. I think this may work.
public ActionResult Send(FormCollection frm)
{
var time = frm['Transaction.Time'];
}
Put all fields inside the form
#using (Html.BeginForm("Send", "DepositDetails", FormMethod.Post))
and make sure that the model
BitcoinTransactionViewModel
included in view or not?
Can you just combine those 2 models you have? Here's how I do it with one model per view...
1. I use Display Templates from view to view so I can pass the whole model as well as leave data encrypted..
2. Setup your main view like this...
#model IEnumerable<LecExamRes.Models.SelectionModel.GroupModel>
<div id="container">
<div class="selectLabel">Select a Location:</div><br />
#foreach (var item in Model)
{
#Html.DisplayFor(model=>item)
}
</div>
3. Create a DisplayTemplates folder in shared. Create a view, naming it like your model your want to pass because a DisplayFor looks for the display template named after the model your using, I call mine GroupModel. Think of a display template as an object instance of your enumeration. Groupmodel Looks like this, I'm simply assigning a group to a button.
#model LecExamRes.Models.SelectionModel.GroupModel
#using LecExamRes.Helpers
#using (Html.BeginForm("Index", "Home", null, FormMethod.Post))
{
<div class="mlink">
#Html.AntiForgeryToken()
#Html.EncryptedHiddenFor(model => model.GroupKey)
#Html.EncryptedHiddenFor(model => model.GroupName)
<p>
<input type="submit" name="gbtn" class="groovybutton" value=" #Model.GroupKey ">
</p>
</div>
}
4. Here's the Controller.
*GET & POST *
public ActionResult Index()
{
// Create a new Patron object upon user's first visit to the page.
_patron = new Patron((WindowsIdentity)User.Identity);
Session["patron"] = _patron;
var lstGroups = new List<SelectionModel.GroupModel>();
var rMgr = new DataStoreManager.ResourceManager();
// GetResourceGroups will return an empty list if no resource groups where found.
var resGroups = rMgr.GetResourceGroups();
// Add the available resource groups to list.
foreach (var resource in resGroups)
{
var group = new SelectionModel.GroupModel();
rMgr.GetResourcesByGroup(resource.Key);
group.GroupName = resource.Value;
group.GroupKey = resource.Key;
lstGroups.Add(group);
}
return View(lstGroups);
}
[ValidateAntiForgeryToken]
[HttpPost]
public ActionResult Index(SelectionModel.GroupModel item)
{
if (!ModelState.IsValid)
return View();
if (item.GroupKey != null && item.GroupName != null)
{
var rModel = new SelectionModel.ReserveModel
{
LocationKey = item.GroupKey,
Location = item.GroupName
};
Session["rModel"] = rModel;
}
//So now my date model will have Group info in session ready to use
return RedirectToAction("Date", "Home");
}
5. Now if I've got alot of Views with different models, I typically use a model related to the view and then a session obj that grabs data from each model so in the end I've got data to submit.
The action name to which the data will be posted should be same as the name of the action from which the data is being posted. The only difference should be that the second action where the data is bein posted should have [HttpPost] and the Posting method should serve only Get requests.

Form in Partial not binding to model

I've got a page basically displaying an article backed by a database.
Below that article, there is a comments section. This is provided by a #Html.Action call returning a _Comments partial.
Within that _Comments partial. There is an optional _AddComment #Html.Action call that renders a _AddComment partial within it.
The _AddComment partial is backed by _AddComment controller methods for GET and POST.
[HttpPost]
[ValidateAntiForgeryToken()]
public ActionResult _AddComment(EditComment comment)
The GET method just returns an "EditComment" VM with the AssetID attached.
Whenever a comment is filled in and posted within the _AddComment view. It's controller method is called correctly, but the model isn't passed back.
If I look at the Request parameters I can see all the properties of the model being passed back correctly. However, it's not being bound into the Controllers method parameter.
I've tried specifying "Model" as the route params for the Html.Begin form. It's made no difference.
Have looked at a number of SO posts, none of which sort the issue I'm having!
Presumably the model binding is failing somewhere for some reason. But obviously without an exception I've no idea what's wrong!
View Model Code
public class EditComment
{
public Boolean HasRating { get; set; }
public int AssetID { get; set; }
public int CommentID { get; set; }
public int Rating { get; set; }
public string Comment { get; set; }
}
View Code
#model SEISMatch.UI.Models.Content.EditComment
<hr />
<h3><span class="colored">///</span> Leave a Comment</h3>
<div class="row" style="margin-top: 20px;">
#using (Html.BeginForm("_AddComment", "Content", Model, FormMethod.Post))
{
#Html.ValidationSummary(false)
#Html.AntiForgeryToken()
#Html.HiddenFor(m => m.AssetID)
#Html.HiddenFor(m => m.CommentID)
if (Model.HasRating)
{
#Html.EditorFor(m => m.Rating, "_StarRating")
}
<div class="span7">
#Html.TextAreaFor(m => m.Comment, new { #class = "span7", placeholder = "Comment", rows = "5" })
</div>
<div class="span7 center">
<button type="submit" class="btn btn-success">Post comment</button>
</div>
}
</div>
Your action parameter name is comment and class EditComment has Property Comment. Modelbinder gets confused.
Rename your action parameter and problem solved.
[HttpPost]
[ValidateAntiForgeryToken()]
public ActionResult _AddComment(EditComment model)

Categories