So this is strange. I have a model composed of person and List<person>. when the user submits the view back to the controller, person is add to the List<person> and then we clear out all the person fields by the means of a person=new Person();.
I would expect that when return to the view, all the person fields are cleared out and the view is a "fresh" start. However, for some strage reason I cannot figure out, most of the fields for person are still filled out with the previous values (even after the person=new Person();).
The model is a complex model, composed of several "objects of objects", and some of the objects inherit from other objects. Still, I can't understand why the view still shows values from previous posts.
EDIT !!!!!
I am posting via a regular form post (HTML.BeginForm()). So here is my controller:
[HttpPost]
[ValidateAntiForgeryToken()]
public ActionResult sendInscriptorRequest(inscriptionModel _model)
{
var _umbracoModel = Umbraco.TypedContentAtRoot().FirstOrDefault();
_model = bllInscripcion.fillModel(_model);
_model = _model.Map(_umbracoModel);
if (_model.formAction == "addParticipants")
{
_model.participants.Add(_model.newParticipant);
_model.newParticipant = new participantModel();
_model.ui.participants.btnTotalParticipantsNumber += 1;
return View("addParticipants", _model);
}
else
{
_model.newParticipant = bllInscripcion.preFillParticipantContactWithInscriptorContact(_model);
return View("formularioInscripcion2", _model);
}
}
You trying to return the view with the model from the POST action method. That won't work because the intended behavior is to return the model state that was posted so the user can make corrections.
If you're posting data and then want to return an empty view you should redirect to the GET action method that returns the initial state of the view. It's known as a Post-Redirect-Get pattern.
http://en.wikipedia.org/wiki/Post/Redirect/Get
Following this pattern will also solve the problem of the user refreshing the page following the POST and getting that annoying dialog that says "You're about to resubmit data. Are you sure?" to which the user always responds "Yes" and then your system has to deal with the potential duplication.
Explanation of the problem:
This is because of ModelState. By default, MVC will retain the model state within a post action if you're returning the same view. To prevent this, simply clear it before returning the view:
ModelState.Clear();
This will mean that whatever model you pass back to the view will be used, not the one stored in model state.
DISCLAIMER:
You should use ModelState.Clear only when needed. Judging by your code, you don't need to use it. Simply redirecting to another page after you have performed the necessary logic is the better thing to do here (as per the PRG pattern).
Related
Right, so a) I'm not sure if this is the right forum for this question, I failed to find a better suggestion, and b) I couldn't find an answer to a question I can't believe I haven't asked before:
So if I have a model, standard form to fill in and it errors so I throw it back to the user, how do I, or how SHOULD I, structure the code to make sure all the supporting properties are constructed?
Example:
public ActionResult DoThing()
{
var model = new DoThingModel();
model.Things = ThingService.FetchThings();
//Do Other Things
return View(model);
}
The fairly standard GET action
[HttpPost]
public ActionResult DoThing(DoThingModel model)
{
var valid = ThingService.ValidateThing(model);
if(!valid)
{
model.ValidationMessage = "Ohh no validation failed";
model.valid = false;
return View(model); //I have't rebuilt model.Things with ThingService.FetchThings()
}
else
{
return RedirectToAction("Success");
}
}
So if the thing is valid redirect to the success page, but otherwise return the form (contained within the model) to the user with a validation message. Problem is my form page has things on it and I haven't populated the model with it.
Now, this is a very simple example and I could just pass back things in the form, or duplicate the line of code that populates it. But imagine I have a much more complicated model, with breadcrumbs, multiple properties that need populating. Code duplication = bad, so how SHOULD I be making sure the model has everything I need before passing it back? I thought about creating a service but it's a service with one function. I thought about using a constructor, but that doesn't play well with model binding. I thought about creating a DoThingModel.Init() that does all the work, but putting code like that inside a model class seems dirty. I played around with a private function in the Controller that filled in all the required properties, but this seems dirty again.
I feel like there should be a standard code pattern that goes => Create New Model => Make sure Model has everything that supports the page => return Model => validate Model => re-apply any properties => return Model => repeat until valid.
Like I said in the intro, this must be a standard thing somewhere, but I can't find it.
I am using asp.net core 2.2 and trying to implement the pattern outlined at POST-REDIRECT-GET using TempData in ASP.NET Core. I have two actions as follows:
[ImportModelState]
public async Task<IActionResult> Upload(int id, CancellationToken ct)
{
var model = new MyModel();
// ...
return this.View(model);
}
[HttpPost]
[ExportModelState]
public async Task<IActionResult> Upload(int id, MyModel model, CancellationToken ct)
{
// ...
}
After accessing the get view, entering the form details and submitting, I set a break point in the post action to view the bound model data for the MyModel parameter and I see an array for a boolean Replace field which backs a checkbox in the views form: However the Replace field works and if the model state is valid, everything succeeds for both of the form checkbox states.
If the form is invalid and I serialize the modelstate to tempdata and merge it on a redirect to the get action, I get an exception System.InvalidOperationException: The parameter conversion from type 'Newtonsoft.Json.Linq.JArray' to type 'System.Boolean' failed because no type converter can convert between these types.
Why does the model binder create an array for the boolean field (is this due to the semantics for forms and check boxes?) which works on the post action, but the same data fails to bind against the same model type when repopulating the view?
The serialization to temp data uses the following logic:
var errorList = modelState
.Select(kvp => new ModelStateTransferValue
{
Key = kvp.Key,
AttemptedValue = kvp.Value.AttemptedValue,
RawValue = kvp.Value.RawValue,
ErrorMessages = kvp.Value.Errors
.Select(p => p.ErrorMessage)
.ToList(),
});
The deserialization and merging uses the following logic:
var errorList = JsonConvert.DeserializeObject<List<ModelStateTransferValue>>(serialized);
var modelState = new ModelStateDictionary();
foreach (var item in errorList)
{
modelState.SetModelValue(item.Key, item.RawValue, item.AttemptedValue);
foreach (var error in item.ErrorMessages)
{
modelState.AddModelError(item.Key, error);
}
}
filterContext.ModelState.Merge(modelState);
The issue is that you aren't actually following post-redirect-get. The redirect happens on success. If there's validation errors, you just return the view again. No need to persist anything in TempData. Even the author of the linked article points out:
As PRG is primarily intended to prevent double form submissions, it does not necessarily follow that you should REDIRECT a user if the form is invalid. In that case, the request should not be modifying state, and so it is valid to submit the form again.
Where he goes astray is in the whole use TempData for invalid submissions and still redirect thing. That's just patently wrong, and I actually find it a little surprising that that advice is coming from Andrew Lock. I've referenced his articles quite often, but apparently never noticed this one.
In software development, there's an unspoken golden rule: don't be a unicorn. Doing things differently than virtually every other developer on the planet doesn't make you unique and special; it makes you an idiot.
UPDATE
Wow. Just reading through the comments and came across one from Andrew where he says:
Having said that, I haven't actually personally used this approach - as you say it adds a lot of complexity
That's a done deal right there. I mean it's an interesting thought experiment, but if it's not good enough for the guy who wrote it, it's not good enough for anyone else either.
All this does is get rid of the "Confirm form resubmission" dialog when a user refreshes a page that was submitted with invalid data. The case where the post is successful is already covered by PRG in general. Given the complexity, and all the extra requests, it's just not even remotely worth it for something that might not even ever happen. Personally, I've found users to be afraid to refresh the page, fearing that they'll have to re-enter all the fields again (which ironically this solution would actually cause, whereas they'd have been just fine refreshing otherwise).
I'm completely new to MVC, moving from Webforms which I know well.
I've got the basics and now trying to work with a database. I can send data to the view no problem but how do I get the edited back?
I'm seeing lots of examples like this:
public ActionResult Update(MyObject myObject)
{
//do database EFW here
}
I can't work out how you get the data from the web-form(view) into myObject. I'm not doing anything clever I have followed a few examples on line but none explain how this mechanism works.
For the form I'm using #Html.BeginForm and helper functions (Html.TextBoxFor) etc, to create my form fields. The problem is now I can't get the edited data back so I can update my data. I hit the right controller action.
The only thing that's different is my form a is a partial view inside a bootstrap dialog.
UPDATE
Well I finally have a object back in my action method, however it doesn't have the key field populated, only the things on the form so I still can't save it (as an update) because I don't know which one it is.
I get and send this object to the view
var diary = new mySQLEntities().Diaries.Single(d => d.DRefno == Id);
and all is good, then what I want to do is this, or something similar
public ActionResult DiaryItemUpdate(mymvc.Diary Diary)
{
mySQLEntities db = new mySQLEntities();
db.Diaries.Add(Diary);
db.SaveChanges();
return RedirectToAction("Index", "Home");
}
[Bind(Include = ... )] doesn't work, it just deletes ALL the properties, even the ones I was getting back.
All the examples I look at seem to do the same thing as I'm doing but it just doesn't work.
I thought this was going to be easy but it isn't. I looked at EF a while back and ditched it for this very reason. It says it makes development easy, it's making it a lot harder for me. I can see why I stuck with web-forms for so long.
Any properties you pass in need to be instantiated in the form if you want them passed back. You need to add the ID of the object as a hidden form element:
#Html.HiddenFor(model => model.ID)
This will ensure that the ID for the object you sent to the view gets posted back.
This is more of a high-level question than anything else.
I have a MVC project which, among other things, manages users.
I am currently sticking to a more strict approach that on my view pages I only use a model which is declared, meaning that I have a:
#model MVCApp.Models.SomeModel
At the top of each View.cshtml
There are some pages that require more than 1 model. At this point I consolidate the 2 models into 1, and send it to the view as one model.
Here comes the tricky part. Let's say I have some model which holds the user data. That user data is stored in the session cookies (typical Forms Authentication). It seems weird to me that I now have to wrap each and every model I use with my own model that holds both the User Model and the model which I want to use inside that View.
The question that I ask myself is why not pass the User Model to the ViewBag and use it inside the View. Why is that considered to be bad practice? It allows me to attach that model to every page without having to ultimately duplicate all my models.
I'd love to get some guidance. I might be looking at this the wrong way. Any help will be much obliged.
Thanks,
There are a couple of reasons why ViewBag should be avoided:
ViewBag is weakly typed
You don't get Intellisense in the view
Your view is a mixture of ViewBag and a view model and your view gets information from different places instead of centralizing everything into a strongly typed view model
ViewBag doesn't work with strongly typed helpers and expressions because dynamic types cannot be used with extension methods (that's not a limitation of ASP.NET MVC, it's .NET => you cannot dispatch extension methods on dynamic types)
Due to this weak typing nature of ViewBag you will have to cast in your views turning them into spaghetti code because HTML helpers expect specific types to be passed as arguments
... this list goes on (I really don't have time to fill it but it is very large list)
So now that we know that ViewBag is bad and shouldn't be used there are different ways you could solve this requirement by using view models.
One possibility is to use the Html.Action helper which allows you to insert some portion of HTML in the view by going through a full Controller/Model/View lifecycle. This way you could have a commonly used widget without interfering with your main view model.
Another possibility is to have a base view model which will contain a property representing user details and which will be populated by a custom global action filter that could executed everytime a controller action has finished executing and returned a view result. The action filter could intercept this view result, read the authentication cookie information and set the view model property. This assumes that all your view models derive from the common base view model. This obviously makes sense if you need to display this user information on each page.
For example:
public class UserInfoInjectorAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var result = filterContext.Result as ViewResultBase;
if (result == null)
{
// the controller action didn't return any view result => no need to continue
return;
}
var model = result.Model as BaseViewModel;
if (model == null)
{
// the controller action didn't pass a model or the model passed to the view
// doesn't derive from the common base view model that will contain
// the user info property => no need to continbue any further
return;
}
model.UserInfo = ... go ahead and read the forms authentication cookie
userData portion and extract the information
you are looking for
}
}
Now all that's left is to register this action filter as a global action filter and it will be applied on all controller actions.
Now if all your view models derive from this BaseViewModel you will know that once you arrive in the view the UserInfo property will be populated with the relevant information without polluting all your controller actions with code that does the fetching of this property. And still you get strong typing in the view => no ViewBag (youpeeee).
Of course depending on your specific scenario there might be other ways to achieve that (obviously not involving any ViewBag).
You can an ActionFilterAttribute and in those Actions that load views which needs specific ViewBag items, you initialize them.
I don't recommend it, as it will be harder to mantains, but will be a generic approach which may solve your problems.
I'm relatively new to view models and I'm running into a few problems with using them. Here's one situation where I'm wondering what the best practice is...
I'm putting all the information a view needs into the view model. Here's an example -- please forgive any errors, this is coded off the top of my head.
public ActionResult Edit(int id)
{
var project = ProjectService.GetProject(id);
if (project == null)
// Something about not found, possibly a redirect to 404.
var model = new ProjectEdit();
model.MapFrom(project); // Extension method using AutoMapper.
return View(model);
}
If the screen only allows editing of one or two fields, when the view model comes back it's missing quite a bit of data (as it should be).
[HttpPost]
public ActionResult Edit(int id, ProjectEdit model)
{
var project = ProjectService.GetProject(id);
if (project == null)
// Something about not found, possibly a redirect to 404.
try
{
if (!ModelState.IsValid)
return View(model) // Won't work, view model is incomplete.
model.MapTo(project); // Extension method using AutoMapper.
ProjectService.UpdateProject(project);
// Add a message for the user to temp data.
return RedirectToAction("details", new { project.Id });
}
catch (Exception exception)
{
// Add a message for the user to temp data.
return View(model) // Won't work, view model is incomplete.
}
}
My temporary solution is to recreate the view model from scratch, repopulate it from the domain model, reapply the form data to it, then proceed as normal. But this makes the view model parameter somewhat pointless.
[HttpPost]
public ActionResult Edit(int id, ProjectEdit model)
{
var project = ProjectService.GetProject(id);
if (project == null)
// Something about not found, possibly a redirect to 404.
// Recreate the view model from scratch.
model = new ProjectEdit();
model.MapFrom(project); // Extension method using AutoMapper.
try
{
TryUpdateModel(model); // Reapply the form data.
if (!ModelState.IsValid)
return View(model) // View model is complete this time.
model.MapTo(project); // Extension method using AutoMapper.
ProjectService.UpdateProject(project);
// Add a message for the user to temp data.
return RedirectToAction("details", new { project.Id });
}
catch (Exception exception)
{
// Add a message for the user to temp data.
return View(model) // View model is complete this time.
}
}
Is there a more elegant way?
EDIT
Both answers are correct so I would award them both if I could. The nod goes to MJ however since after trial and error I find his solution to be the leanest.
I'm still able to use the helpers too, Jimmy. If I add what I need to be displayed to the view bag (or view data), like so...
ViewBag.Project= project;
I can then do the following...
#Html.LabelFor(model => ((Project)ViewData["Project"]).Name)
#Html.DisplayFor(model => ((Project)ViewData["Project"]).Name)
A bit of a hack, and it requires the domain model to be decorated with System.ComponentModel.DisplayNameAttribute in some cases, but I already do that.
I'd love to call...
#Html.LabelFor(model => ViewBag.Project.Name)
But dynamic causes a problem in expressions.
After some trial-and-error (aka code it, then hate it) learning, my currently preferred approach is:
I use view-models only to bind input fields. So in your case, if your view is only editing two fields, then your view-model will only have two properties. For the data required to populate the view (drop-down lists, labels, etc), I use the dynamic ViewBag.
I believe that displaying the view (i.e. populating anything the view needs to display), and capturing the posted form values (binding, validation, etc) are two separate concerns. And I find that mixing the data required to populate the view with that which is posted back from the view gets messy, and creates exactly your situation more often than not. I dislike partially populated objects being passed around.
I’m not sure how this plays out with Automapper (for mapping the domain object to the dynamic ViewBag) though, as I haven’t used it. I believe it has a DynamicMap method that may work? You shouldn’t have any issues auto-mapping the posted strongly-typed ViewModel onto the Domain object.
If I understand correctly, your viewmodel probably looks very similar to your domain entity. You mentioned that the viewmodel can come back mostly empty because only certain fields were editable.
Assuming you have a view where only a few fields are available for edit (or display), these are the only fields you should make available in your viewmodel. I usually create one viewmodel per view, and let either the controller or a service handle the user's input and map it back up with the domain entity after performing some validation.
Here's a thread concerning best-practices for viewmodels that you might find useful.
Edit: You can also accept a different view model in your Edit/POST action than your Edit/GET action serves up. I believe this should work as long as the model binder can figure it out.