post ignoring Model changes Razor ASP.NET - c#

I have a controller which returns a Partial View with a Model.
The view that contains the partial View has a button, When clicking on the button, a function of the controller is called and it returns the same partial View with the model updated. The new model is loaded without any problem, but the page doesn't reload, the view is the same view as before the onclik.
The code of partial View
<div class="well">
#if (publication.Coments != null) {
foreach (var comments in publication.Coments) {
<div class="row">
<div class="col-md-12">
#comments.Nick
<span class="pull-right">#comments.DateComment.ToShortDateString()</span>
<p>#comments.Message</p>
</div>
</div>
}
}
</div>
The method of controller return the partial view with the next code :
ViewData["publication"] = publication;
return PartialView("details_comment");
I call the partial view in the view :
#Html.Partial("../Home/ListPublication")
I debugged the page and the model is reloaded ok, but the partial view doesn't reload.

I mentioned on the comments I was having the same issue but later today I figured at MSDN that this is an expected behaviour if you are returning the same model and view types after a POST. For my scenario I had to use ModelState.Clear() before changing any values on the returning view model. To explain a little better, I'll try to describe my case as close as I can remember to try to contextualize:
view models
// ~/Models/SomeFeatureModels.cs
public class SomeViewModel {
public int Id {get;set;}
public string SomeField{get;set;}
public string SomeOtherField{get;set;}
public DateTime CreatedOn{get;set;}
}
public class SomeOtherViewModel {
public int Id {get;set;}
public string SomeField{get;set;}
public string SomeOtherField{get;set;}
public DateTime CreatedOn{get;set;}
}
public class IndexViewModel {
public string FeatureTitle{get;set;}
}
templates
<!-- ~/Views/Some/SomeInfo.cshtml -->
#model.App.Models.SomeInfoViewModel
#using(Html.BeginForm("AddSomeInfo", "Some", FormMethod.Post, new { #id="frmAddSomeInfo" }) {
<div id="some-info">
#Html.DisplayFor(m=>m.SomeField)
#Html.EditorFor(m=>m.SomeField)
#Html.ValidatorFor...
<input type="submit">Go</input>
</div>
}
<!-- ~/Views/Some/SomeInfo.cshtml -->
#model.App.Models.SomeOtherInfoViewModel
#using(Html.BeginForm("AddSomeOtherInfo", "Some", FormMethod.Post, new { #id="frmAddSomeOtherInfo" }) {
<div id="some-other-info">
#Html.DisplayFor(m=>m.SomeField)
#Html.EditorFor(m=>m.SomeField)
#Html.ValidatorFor...
<input type="submit">Go</input>
</div>
}
<!-- ~/Views/Some/Index.cshtml -->
#model App.Models.IndexViewModel
#{
layout: "someLayout.cshtml"
}
<h2>Model.FeatureTitle</h2>
#{ RenderAction("SomeInfo") }
#{ RenderAction("SomeOtherInfo") }
#section scripts {
//bundle must include:
// jquery, jquery.unobtrusive.ajax, jquery.validate, jquery.validate.unobtrusive
<script>
$(function() {
$('#frmAddSomeInfo').submit(function(e) {
e.preventDefault();
var form = $(this);
if (form.valid()) {
$.ajax({
url: form.action,
type: form.method,
data: form.serialize()
}).done(function(payload) {
$('#some-info').html(payload);
}).fail(function(jqXHR, error, errorThrown) {
// handle
});
}
});
$('#frmAddSomeOtherInfo').submit(function(e) {
e.preventDefault();
var form = $(this);
if (form.valid()) {
$.ajax({
url: form.action,
type: form.method,
data: form.serialize()
}).done(function(payload) {
$('#some-other-info').html(payload);
}).fail(function(jqXHR, error, errorThrown) {
// handle
});
}
});
});
</script>
}
controller
// ~/Controllers/SomeController.cs
public class SomeController: Controller {
// This would be the landing view of a given controller
public ActionResult Index() {
// for this view model I have basically typed the things that
// are a concern of Index, like page title and other things
// but nothing related to the view models that I will be
// updating or inserting
var viewModel = somePrivateMethodToBuildMyViewModel();
return View(viewModel);
}
public PartialViewResult SomeInfo() {
// this is technically a fresh instance with normalized
// or localized default data that I will be sending
// when the index requests this partial view
var someViewModel = somePrivateMethodToBuildSomeViewModel();
return PartialView(someViewModel);
}
[HttpPost]
public PartialViewResult AddSomeInfo(SomeViewModel viewModel) {
// this is where I am checking if my view model is alright
// and then the "issue" will occur
if (!ModelState.IsValid) {
// ... handle
} else {
// I was doing "normal stuff" like
// translating view model to an entity (let's call it model)
// committing changes with some ORM and get and id and timestamp back
// and naturally assign these values to the view model
viewModel.Id = model.id;
viewModel.createdOn = model.created_on;
}
// then return the same view and same model **types** of the request
return PartialView("SomeInfo", viewModel);
}
}
This is the part that I had to use ModelState.Clear(). I've changed my POST action to this:
// ~/Controllers/SomeController.cs
public class SomeController: Controller {
// ...
[HttpPost]
public PartialViewResult AddSomeInfo(SomeViewModel viewModel) {
if (!ModelState.IsValid) {
// ... handle
} else {
// Included this line for valid model state handling
ModelState.Clear();
// then persist, etc
// then change view model
viewModel.Id = model.id;
viewModel.createdOn = model.created_on;
}
// then returning the view model to the same partial should now work
return PartialView("SomeInfo", viewModel);
}
}
Sorry this got a little too much, but I just wanted to show how I got it working on my scenario.

Try like below.
return PartialView("details_comment", publication);

You need to pass the model's object with the partial view to see the value, as your model will get bound with the helpers to create a view
return PartialView("details_comment",publication);

Updated
Instead of relative URL:
Try: #Html.Partial(#"~/Views/Home/ListPublication.cshtml") or use
#{Html.RenderPartial("ListPublication");}
The other thing I noticed is that you are using old ViewData. I am not saying you should not use it at all but a better approach is to use a strongly typed ViewModel. A View Model is a class that will have necessary properties and logic for your view to render. So you could have a class:
public class PublicationVM{public Publication publication {get; set;} }
and can have other necessary properties if needed by the view. SO has lot of information about using View Model. Please check What is ViewModel in MVC?

Related

ASP.NET Core MVC Model Binding Bug?

I'm having some trouble with ASP.NET Core's model binding. Basically I'm just trying to bind some POSTed json to an object model with nested properties. Minimal code is provided below for a single button that, when pressed, will POST to a Controller action method via XMLHttpRequest. The action method takes a single model class parameter with the [FromBody] attribute.
Model:
public class OuterModel {
public string OuterString { get; set; }
public int OuterInt { get; set; }
public InnerModel Inner { get; set; }
}
public class InnerModel {
public string InnerString { get; set; }
public int InnerInt { get; set; }
}
Controller:
using Microsoft.AspNetCore.Mvc;
public class HomeController : Controller {
[HttpPost("models/")]
public IActionResult Save([FromBody] OuterModel viewModel) {
if (!ModelState.IsValid)
return BadRequest(ModelState);
// Return an appropriate response
return Ok();
}
}
Razor markup for a "submit" button:
<div class="form-row">
<div class="col-2">
#{ string url = Url.Action(nameof(HomeController.Save), "Home"); }
<button id="post-btn" data-post-url="#url">POST</button>
</div>
</div>
JavaScript to submit (DOES NOT bind):
document.getElementById("post-btn").addEventListener("click", e => {
const xhr = new XMLHttpRequest();
xhr.addEventListener("timeout", () => console.error("Timeout"));
xhr.addEventListener("error", () => console.error("Error"));
xhr.addEventListener("load", () => console.log(`Status: ${xhr.status} ${xhr.statusText}`));
xhr.open("POST", e.target.dataset.postUrl);
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xhr.send(JSON.stringify({
"OuterString": "outer",
"OuterInt": 1,
"Inner.InnerString": "inner",
"Inner.InnerInt": 5
}));
});
Looking at that JavaScript, I would expect the Inner.* json property names to bind correctly, given this line from the docs:
Model binding looks for the pattern parameter_name.property_name to bind values to properties. If it doesn't find matching values of this form, it will attempt to bind using just the property name.
But it doesn't; the OuterModel.Inner property in the action method ends up null. The following json does bind correctly, however:
JavaScript to submit (DOES bind):
xhr.send(JSON.stringify({
"OuterString": "outer",
"OuterInt": 1,
"Inner": {
"InnerString": "inner",
"InnerInt": 5
}
}));
So I can use this code to achieve the model binding that I need, I'm just confused as to why the first JavaScript didn't work. Was I not using the correct naming convention for nested properties? Some clarification would be much appreciated!

ASP.NET mvc RenderAction Login And Register Views

I am working on NopCommerce v3.80. By Default the login and register views are different. I needed to merge them without changing much of the code, so I called #{ RenderAction("Register"); } inside Login.cshtml.
I also removed the layout (Layout = "~/Views/Shared/_ColumnsOne.cshtml";) from Register view.
the problem is when a validation error like 'Email Id already Exists!' comes, it goes to the register view. I need to show the validation or error message on the login view. but the login view accepts only Login Model.
Please see my code :
Register.cshtml
#model RegisterModel
#using Nop.Web.Models.Customer;
#{
//Layout = "~/Views/Shared/_ColumnsOne.cshtml";
}
<!-- Registeration fields -->
Login.cshtml
#model LoginModel
#using Nop.Web.Models.Customer;
#{
Layout = "~/Views/Shared/_ColumnsOneTT.cshtml";
}
#using (Html.BeginForm("Register", "Customer", FormMethod.Post)){
<div>
#{
Html.RenderAction("Register");
}
<input type="submit" value="Submit"/>
}
CustomerController.cs - Register Method
public ActionResult Register(RegisterModel model){
// Lot of code
if (success){
// lot of code
return RedirectToRoute("RegisterResult");
}
foreach (var error in registrationResult.Errors)
ModelState.AddModelError("", error);
PrepareCustomerRegisterModel(model, true, customerAttributesXml);
return View(model);
}
UPDATE : I checked how to work with two forms in a single view but it won't help me as I can not go with new model creation option.
UPDATE 2: I also tried with the new model creation option which covers the login and register models but I am still getting the same result.
What you are trying to do is not really doable without more significant code changes then you have already done, since the validation is scoped to the specific model you are handling.
Probably the simplest effective solution I can think of, would be changing the Model of the Login view to be a wrapper around the other two models.
public class AuthenticationModel {
public LoginModel Login {get;set;}
public RegisterModel Register {get;set;}
public AuthenticationModel (LoginModel lModel, RegisterModel rModel) {
Login = lModel;
Register = rModel;
}
}
This should solve most of the problem you are having, though the validation messages would have to be correctly targeted at your structure:
ModelState.AddModelError("", error);
in your code is replaced by:
ModelState.AddModelError("Register", error);
Thank you all for your efforts.I had to create new model and wrap the two models Register and Login inside that.
it looks like how to work with two forms in a single view helped me a lot.
However, I am posting complete solution for a newbie.
CustomerController:
Loading the page
public ActionResult Login(bool? checkoutAsGuest)
{
var loginModel= new LoginModel();
loginModel.UsernamesEnabled = _customerSettings.UsernamesEnabled;
loginModel.CheckoutAsGuest = checkoutAsGuest.GetValueOrDefault();
loginModel.DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnLoginPage;
var registerModel = new RegisterModel();
PrepareCustomerRegisterModel(registerModel, false);
registerModel.Newsletter = _customerSettings.NewsletterTickedByDefault;
return View(new LoginRegisterModel { LoginModel = , RegisterModel = registerModel });
}
Login POST :
public ActionResult Login(LoginModel model, string returnUrl, bool captchaValid)
{
// Previous code as it is
// Important! I Added this new line - to handle validation problems
ModelState.Add("LoginValidation", null);
//If we got this far, something failed, redisplay form
model.UsernamesEnabled = _customerSettings.UsernamesEnabled;
model.DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnLoginPage;
var registerModel = new RegisterModel();
PrepareCustomerRegisterModel(registerModel, false);
//enable newsletter by default
registerModel.Newsletter = _customerSettings.NewsletterTickedByDefault;
return View(new LoginRegisterModel { LoginModel = model, RegisterModel = registerModel });
}
Register:
public ActionResult Register()
{
//check whether registration is allowed
if (_customerSettings.UserRegistrationType == UserRegistrationType.Disabled)
return RedirectToRoute("RegisterResult", new { resultId = (int)UserRegistrationType.Disabled });
var model = new RegisterModel();
PrepareCustomerRegisterModel(model, false);
model.Newsletter = _customerSettings.NewsletterTickedByDefault;
var loginModel = new LoginModel();
loginModel.UsernamesEnabled = _customerSettings.UsernamesEnabled;
loginModel.DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnLoginPage;
return View("Login", new LoginRegisterModel { RegisterModel = model, LoginModel = loginModel });
}
Register POST :
public ActionResult Register(RegisterModel model, string returnUrl, bool captchaValid, FormCollection form)
{
// previous code as it is
// added this line to handle validations
ModelState.Add("RegisterValidation", null);
//If we got this far, something failed, redisplay form
PrepareCustomerRegisterModel(model, true, customerAttributesXml);
var loginModel = new LoginModel();
loginModel.UsernamesEnabled = _customerSettings.UsernamesEnabled;
//loginModel.CheckoutAsGuest = checkoutAsGuest.GetValueOrDefault();
loginModel.DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnLoginPage;
return View("Login", new LoginRegisterModel { RegisterModel = model, LoginModel = loginModel });
}
Then created two partial Views - _LoginModel.cshtml and _registerModel.cshtml. In the views I added only one extra line
_LoginModel.cshtml:
if (!MvcHtmlString.IsNullOrEmpty(validationSummary) && ViewData.ModelState.ContainsKey("LoginValidation"))
{
<div class="message-error">#validationSummary</div>
}
_RegisterModel.cshtml
#if (!MvcHtmlString.IsNullOrEmpty(validationSummary) && ViewData.ModelState.ContainsKey("RegisterValidation"))
{
<div class="message-error">#validationSummary</div>
}
and finally, Login page
Login.cshtml
replaced register button (left panel with )
#using (Html.BeginForm("Register", "Customer", FormMethod.Post)){
<!-- divs and other elements -->
#Html.Partial("_RegisterModel", Model.RegisterModel)
<!-- divs and other elements -->
}
and login form with
#using (Html.BeginForm("Login", "Customer", new { returnUrl = Request.QueryString["returnUrl"] }, FormMethod.Post))
{
<!-- divs and other elements -->
#Html.Partial("_LoginModel", Model.LoginModel)
<!-- divs and other elements -->
}
Apart from combining models into a view model to use that encompasses both models (like you probably should be doing in this case) a cheap and easy option would be to detect the error and pass it back as a viewbag property. Then on the front end you can check if that property is not null and display an error message if it is.
You should be creating a viewmodel though, everything else is more of a band-aid workaround.
You could merge the login and register models into a brand new one,so validations could be adressed to the desired model.

Does model maintains its structure when data is received in controller?

I am not sure whether I've framed the question properly above in subject but I will try to explain to my best about the question I have.
I have below ContactUsModel which is a part of HomeViewModel, better say Nested Model Class in a single model
public class ContactUsDataModel
{
public string ContactName { get; set; }
public string ContactEmail { get; set; }
public string ContactMessage { get; set; }
public string ContactPhone { get; set; }
}
and I am getting this Model referred in HomeViewModel as below:
public class HomeViewModel
{
/*My other models goes here*/
public ContactUsDataModel CUDModel { get; set; }
}
Now in Index.cshtml view I strongly create a form view as below:
#model ProjectName.Models.HomeViewModel
<!--I have other views for other models-->
#using (Html.BeginForm("ContactPost", "Home", FormMethod.Post, new { id = "contactform" }))
{
#Html.TextBoxFor(m => m.CUDModel.ContactName, new { #class="contact col-md-6 col-xs-12", placeholder="Your Name *" })
#Html.TextBoxFor(m => m.CUDModel.ContactEmail, new { #class = "contact noMarr col-md-6 col-xs-12", placeholder = "E-mail address *" })
#Html.TextBoxFor(m => m.CUDModel.ContactPhone, new { #class = "contact col-md-12 col-xs-12", placeholder = "Contact Number (optional)" })
#Html.TextAreaFor(m=>m.CUDModel.ContactMessage, new { #class = "contact col-md-12 col-xs-12", placeholder = "Message *" })
<input type="submit" id="submit" class="contact submit" value="Send message">
}
I do ajax Post as below:
$('#contactform').on('submit', function (e) {
e.preventDefault();
var formdata = new FormData($('.contact form').get(0));
$.ajax({
url: $("#contactform").attr('action'),
type: 'POST',
data: formdata,
processData: false,
contentType: false,
//success
success: function (result) {
//Code here
},
error: function (xhr,responseText,status) {
//Code here
}
});
});
and in Controller I tried to receive it as below:
public JsonResult ContactPost(ContactUsDataModel model)
{
var name=model.ContactName; //null
/*Fetch the data and save it and return Json*/
//model is always null
}
For some reason the above model is always null. But this works if I refer the model as HomeViewModel model instead of ContactUsDataModel model in controller parameter like below:
public JsonResult ContactPost(HomeViewModel model)
{
var name=model.CUDModel.ContactName; //gets value
/*Fetch the data and save it and return Json*/
//Model is filled.
}
My question here is even though I fill model of type
ContactUsDataModel in the view I am getting it as null if I refer
directly, but ContactUsModel which is inside HomeViewModel gets
filled. Doesn't type of model matter here. Is the hierarchy its
referred is necessary while fetching in controller?
Well, if your generated <input> name is CUDModel.ContactName instead of simply ContactName, the default Model-Binder wouldn't be able to bind it.
Fortunately, you can use the [Bind] attribute with prefix:
public JsonResult ContactPost([Bind(Prefix="CUDModel")]ContactUsDataModel model)
{
// ...
}
See MSDN
Your view posts the Type you have referenced in the view - #model ProjectName.Models.HomeViewModel - CUDModel is simply a property of HomeViewModel.
Using your web browser, inspect each DOM input element "name" property. MVC automatically maps properties from your inputs to the class using the input's "name" property.
To solve this you can create a custom model binder or create the inputs by hand, specifying the name property in such a way that the automatic model binder can match them to properties of your class.
However, there isn't anything wrong with your controller action taking HomeViewModel as an argument.
More information, found here.

MVC4 - NullReferenceException being thrown in view when trying to use a list

On my home (index) page I have two partials, one renders a search form and the other results from the search:
<div class="row-fluid well">
<div class="span6">
#Html.Partial("~/Views/Search/_BasicPropertySearchPartial.cshtml")
</div>
<div class="span6" id="basic-property-search-results">
#Html.Partial("~/Views/Search/_BasicPropertySearchResultsPartial.cshtml")
</div>
</div>
In my SearchController a GET action returns the search form:
[HttpGet]
public ActionResult BasicPropertySearch()
{
return PartialView("_BasicPropertySearchPartial");
}
And a POST action gets user input from the form and returns results based on a query:
[HttpPost]
public ActionResult BasicPropertySearch(BasicPropertySearchViewModel viewModel)
{
var predicate = PredicateBuilder.True<ResidentialProperty>();
if (ModelState.IsValid)
{
using(var db = new LetLordContext())
{
predicate = predicate.And(x => x.HasBackGarden);
//...
var results = db.ResidentialProperty.AsExpandable().Where(predicate).ToList();
GenericSearchResultsViewModel<ResidentialProperty> gsvm =
new GenericSearchResultsViewModel<ResidentialProperty> { SearchResults = results };
return PartialView("_BasicPropertySearchResultsPartial", gsvm);
}
}
ModelState.AddModelError("", "Something went wrong...");
return View("_BasicPropertySearchPartial");
}
I've created a generic view model because search results may be lists of different types:
public class GenericSearchResultsViewModel<T>
{
public List<T> SearchResults { get; set; }
public GenericSearchResultsViewModel()
{
this.SearchResults = new List<T>();
}
}
The POST action returns the following view:
#model LetLord.ViewModels.GenericSearchResultsViewModel<LetLord.Models.ResidentialProperty>
#if (Model.SearchResults == null) // NullReferenceException here!
{
<p>No results in list...</p>
}
else
{
foreach (var result in Model.SearchResults)
{
<div>
#result.Address.Line1
</div>
}
}
I've put breakpoints on the GET and POST actions and the exception is being thrown before either or hit.
Is this problem being caused because index.cshtml is being rendered before it has a chance to do the GET/POST in the SearchController?
If so, does this mean it's a routing problem?
Finally, I thought newing SearchResults in the constructor would overcome NullReferenceExceptions?
Feedback appreciated.
It seems that the whole Model is null. You need to either supply a partial view model to the Html.Partial calls or use Html.Action to call the two controller (child) actions with the action and controller names.
See the MSDN for details.

Render Partial View only after HttpPost ActionHandler

I am making WCF service call using MyViewRequest view fields inside HttpPost ActionHandler. The goal is to show response using partial view, MyViewResponse
In brief I need to achieve these two items-
Disable load of partial view on first load.
Display Response (along with Request) after service call.
MyViewRequest.cshtml
#using (Html.BeginForm())
{
#Html.ValidationSummary(false)
//html code
}
</div>
<div id="dvResponse">
#Html.Partial("MyViewResponse");
</div>
Partial view: MyViewResponse.cshtml
#model MvcApplication3.Models.MyModel
#{
ViewBag.Title = "MyViewResponse";
}
<h2>MyView</h2>
#Html.Label(Model.MyName, "My Name")
This was pretty straight forward in Asp.Net using userControl, But stuck up here, How can we achieve this in MVC3.
I think the best way is to transfer your data using ViewModels. Let's assume you want to have an app something like stackoverflow where you have a question and user can post an answer and it will be shown after the post along with the question.
public class PostViewModel
{
public int ID { set;get;}
public string Text { set;get;}
public List<PostViewModel> Answers { set;get;}
public string NewAnswer { set;get;}
}
in your GET action, you show the question. Get the id from the url and get the Question details from your service/repositary.
public ActionResult Show(int id)
{
var post=new PostViewModel();
post=yourService.GetQuestionFromID(id);
post.Answers=yourService.GetAnswersFromQuestionID(id);
return View(post);
}
Assuming yourService.GetQuestionFromID method returns an object of PostViewModel with the propety values filled. The data can be fetched from your database or via a WCF service call. It is up to you. Also yourService.GetAnswersFromQuestionID method returns a list of PostViewModel to represent the Answers for that question. You may put both these into a single method called GetQuestionWithAnswers. I wrote 2 methods to make it more clear.
Now in your Show view
#model PostViewModel
#Html.LabelFor(x=>x.Text);
#using(Html.Beginform())
{
#Html.HiddenFor(x=>x.ID);
#Html.TextBoxFor(x=>x.NewAnswer)
<input type="submit" />
}
<h3>Answers</h3>
#if(Model.Answers!=null)
{
#Html.Partial("Responses",Model.Answers)
}
And your Partial view will be strongly typed to a collection of PostViewModel
#model List<PostViewModel>
#foreach(var item in Model)
{
<div> #item.Text </div>
}
Handling the postback is simple (HttpPost)
[HttpPost]
public ActionResult Show(PostViewModel model)
{
if(ModelState.IsValid)
{
//get your data from model.NewAnswer property and save to your data base
//or call WCF method to save it.
//After saving, Let's redirect to the GET action (PRG pattern)
return RedirectToAction("Show",new { #id=model.ID});
}
}

Categories