MVC ViewModel that lives in Session - how to update? - c#

I have done that the "standard" way:
public ActionResult Respondent()
{
return View(Session["Respondent"]); //passing the model
}
[HttpPost]
public ActionResult Respondent(Respondent resp)
{
repository.UpdateRespondent(Respondent resp);
Session["Respondent"] = respondent; //put back into session
return View(respondent); //redraw view, passing in respondent
}
And it works. I am passing the respondent model only for MVC to collect FORM values automatically, in the POST action, where inside the view I have these for all the properties:
#using (Html.BeginForm())
{
#Html.LabelFor(model => model.FirstName)
#Html.EditorFor(model => model.FirstName)
#Html.ValidationMessageFor(model => model.FirstName)
// and so on...
}
My question is - if I am already using a Session object (that lives in Session),
Is there any way I can use the Session object as a Model inside the view, so that HttpPost works including all the validation. How then, the values will get collected and put back into the Session?
Thank you.

If I understand your question, best practices preaching aside, you CAN pass a session object as a Model.
2 Caveats:
Session objects should be cast when passed
return View((RespondentObject)Session["Respondent"])
On the View, remember to bind to object type
#model perseus.Models.RespondentObject
I recommend you read from and write to Session in your Controllers.
You pointed out that you will be using multiple partials to create your form. You have 2 choice:
Create a Model to be received by Action which includes all objects.
Pass every parameter individually to your Action.
You asked why it is bad form to use object:
Because they're untypes
Because Sessions are flaky (unreliable)
That said, you know your app and architecture best. You have to make your decisions and support them. As much as I'm a purist, I believe best practices are a guide not a religion. Circumstances vary.

Related

How do I get a Strongly typed MVC view to post back the instance of the model it's passed?

So, I'm trying to take a view I wrote to create an instance of a model and use it to edit an existing instance from the database.
So, it's pretty simple to just pass in the existing instance by ID
public ActionResult Index(int id)
{
return View(EntityContext.Policies.Where(x => x.ID == id).First());
}
The problem I'm having is that when the form posts:
#using (Html.BeginForm("Index", "Save", FormMethod.Post, new { #id = "form" }))
{
<fieldset>
...
#Html.TextBox("property")
#Html.TextBoxFor("otherProperty")
...
<input id="submit" type="submit" value="Save" />
</fieldset>
}
The controller that it posts to :
public class SaveController : Controller
{
VectorDaemonContext vdContext = new VectorDaemonContext();
//
// POST: /Save/
[HttpPost]
public ActionResult Index(Policy policy, string property, int otherProperty) {
//Stuff to do with the policy
}
}
The controller gets a Policy object, but it only has the fields set in it that I've shown on the view and allowed to be edited
What I'd like to do is have the view post the instance it gets from the original controller. Is this something I can do simply?
The simplest way I know of to do this would be to make a hidden field on the view for the ID of the Policy, then hit the database to pull back the original instance. I'm already having to hit the database more than I'd like to because of some domain specific stuff, so if I can avoid this, I'd like to.
Correction:
The simplest safest way I know of to do this would be to make a hidden field on the view for the ID of the Policy, then hit the database to pull back the original instance.
If you send the record in its entirety to the client, and then allow that client to post it back, you are giving total control over that record to the client even if you don't "allow" them to edit fields. Because a sneaky client can bypass your app and directly post any data they want and alter those fields. So, this is dangerous and insecure to do. Even if you trust your clients, it's just not a good idea.
The safe thing to do is only take the fields you want them to enter, and apply them to a record you retrieve from the database server-side. This way, only the data you allow them to update gets changed.
However, if you're bound and determined, the way to do this is to create hidden fields for all the variables you do not wish to have the users see or edit. If you want them to see the fields, but not edit, you could use textboxes and make the fields read-only (again, the user could bypass this, so it's not any real security). Or just display the data and use a hidden input.
A web browser will only post data that exists in form controls that are no disabled inside a form element. (or data you send in an ajax request). So any data you want posted back to the server must exist in controls.
I would stick it in a hidden field. And yes, you can simply do it.
#Html.HiddenFor(m => m.ID)
I'm just taking a wild guess you have an ID defined in the model. But you can change it to whatever you want.
You have a several options, some of which you already know:
Pass the id, and re-retrieve from the database
Store the object in the model, hide its values in hidden fields, and pass it back whole
Cache the object somehow, either in the session or another available caching mechanism (Storing an object in the session isn't really a good idea).
Because of the HTTP being stateless, your controller doesn't retain information on which model was sent to what client.

Can i pass another model object to be used in Html.BeginForm?

I have two models which is connected. To simplify lets say I have a Post and Comment. When the details page Post, i want to build a form for posting a comment.
I can do that easily with just plain html. But I wish to use the Html.BeginForm.
First I pass a new Comment object to the details page from the controller.
#{
ViewBag.Title = "Details";
Comment newComment = ViewBag.Comment;
}
#using (Html.BeginForm("Create", "Comments", FormMethod.Post))
{
#Html.EditorFor(newCooment => newComment.Comment, new { htmlAttributes = new { #class = "form-control" }
})
But how can i tell the HtmlHelper to use my Comment model? ("newComment => ... " does not work)
So this happens in your controller. You need to have a view model which is a container for all the objects you want to pass to your view. In your view you then take advantage of the #Model property to access this VM.
public class MyController : Controller
{
public ActionResult Index
{
var myViewModel = new MyViewModel
{
Post = post,
Comment = comment // assumed that these have been read from a DB
};
return View(myViewModel);
}
}
The view model:
public class MyViewModel
{
public Post Post {get;set;}
public Comment Comment {get;set;}
}
In your View:
#model some.namespace.to.viewmodel.MyViewModel
#using(Html.BeginForm())
{
}
#Model <-- this is your MyViewModel instance you created in the controller
#Model.Comment <-- this has your Comment in
#Model.Post <-- this has your Post in
In the controller we do this return View(myViewModel). This is telling the Razor engine that we want to set the #Model to our ViewModel and use it in our page.
I tend to stay clear of helper functions that create a whole bunch of html. I like full control over my HTML so I use the #Html.TextBoxFor() or the #Html.TextAreaFor() those "low level" helpers. If I want absolute control then I just write the HTML my self! The id properties should relate to your object levels:
<input id="Post.Name" type="text" />
It's kind of the architecture of MVC right, so you have Models which define the DB Domain. You have Views which show the model information. You have controllers which should delegate getting the models from the DB and sending them to the page. To get multiple Models to the page we need to use a ViewModel.
The semantics are important and widely used through many systems. Web and Desktop, so they are pretty important to understand.
In my products, I use the N-Tier architecture approach. I have services, DALs, Controllers, ViewModels, Models, DTOs.
DTOs are what I map models to, it stands for Domain Transfer Objects. So I got my Domain Model, I may not want everything on it (I don't use navigation properties for example so my DTOs have those navigation properties), in order to reduce it I create a DTO which is what is used around my system to transport that data. When I want to save that data I map it back to the Domain Model and persist to the database.
A key example is if you are using the ASP.NET Identity stuff. So ASP.NET went down the route of making the authentication EF Code First friendly. If you look at the User model for this, it has a tonne of properties and navigation's that I don't need to use. Especially if you just want to register a user.
My registration DTO will have a field my User does not. ConfirmPassword, I want it on my register so that I can confirm that the original password is what they meant it to be. But it stops at my validation layer, past then, it gets completely dropped - when we've confirmed the passwords match I only need the original password they entered.

passing a view-model with a controller around multiple Views

So I have 3 views, a controller and one model. (just an example) The first view sets the user first name and last name. Which gets posted back to the controller, and I can see the data in the view-model. The controller then calls the second view sets the email (I can call the data from view 1). The third view shows all of the data (the original stuff from view 1 is no longer there)
#Html.DisplayFor(m => m.FirstName)
#Html.DisplayFor(m => m.LastName)
#Html.DisplayFor(m => m.Email)
Do you think creating a static singleton model would work in the controller? or should I be using TempData
EDIT: sorry I forgot about my controller
Would my GET methods in my controller need a parameter?
[HttpGet]
public virtual ActionResult SignUp1(model m)
{
return View(m)
}
you can call into another view using #Html.Partial("view name", object) if you want to preform logic you can call another controller action with #Html.Action("action", "controller", object). then it's just like any other controller action. typically calling actions from a view are decorated with [ChildActionOnly]
Static is a bad idea for web pages, because it is not inherently thread-safe (see here). That means that you'll get really bizarre behavior if you have two or more people using it at once.
I'm not sure why you're even considering doing it this way - is there some specific reason you're thinking about it? The proper way to do it would be to post the model back to each controller action from each view, populating more data each time. Alternatively, you could post back to the same action, and then return the appropriate view based on which fields are missing from the model (and the display if none are).

Using MVC, how do I design the view so that it does not require knowledge of the variables being set by the controller?

Let's say I have a theoretical MVC framework that uses a ViewData object to pass data from the controller to the view. In my controller, let's say I have some code like this (in pseudocode):
function GetClientInfo()
{
// grab a bunch of data from the database
var client = Database.GetClient();
var clientOrders = Database.GetClientOrders();
var clientWishList = Database.GetClientWishList();
// set a bunch of variables in the ViewData object
ViewData.set("client", client);
ViewData.set("clientOrders", clientOrders);
ViewData.set("clientWishList", clientWishList);
showView("ClientHomePage");
}
And then in my ClientHomePage view, I display the data like so:
<p>Welcome back, [ViewData.get("client").FirstName]!</p>
<p>Your order history:</p>
<ul>
[Html.ToList(ViewData.get("clientOrders")]
</ul>
<p>Your wishlist:</p>
<ul>
[Html.ToList(ViewData.get("clientWishList")]
</ul>
This is what I understand MVC to be like (please correct me if I'm wrong). The issue I'm having here is those magic strings in the view. How does the view know what objects it can pull out of the ViewData object unless it has knowledge of what the controller is putting in there in the first place? What if someone does a refactor on one of the magic strings in the controller, but forgets to change it in the view, and gets a runtime bug instead of a compile-time error? This seems like a pretty big violation of separation of concerns to me.
This is where I'm thinking that a ViewModel might come in handy:
class ClientInfo
{
Client client;
List clientOrders;
List clientWishList;
}
Then the controller creates an instance of ClientInfo and passes it to the view. The ViewModel becomes the binding contract between the controller and the view, and the view does not need to know what the controller is doing, as long as it assumes that the controller is populating the ViewModel properly. At first, I thought this was MVVM, but reading more about it, it seems like what I have in mind is more MVC-VM, since in MVVM, the controller does not exist.
My question is, what am I not understanding here about MVC vs. MVVM? Is referring to variables in the ViewData by magic strings really not that bad of an idea? And how does one insure that changes made in the controller won't adversely affect the view?
Your understanding of MVC is wrong, it stands for Model View Controller but you are missing the Model in your example. This is the typed entity that gets passed back to the View to do the rendering. In ASP.Net MVC you would use typed Views that also type the Model within the View so it is checked at compile time. This eliminates the need for magic strings (second part of your question).
In MVVM you have Model View ViewModel. This is a way of binding a ViewModel directly to the UI layer via a View which is used a lot in WPF. It replaces the need for a controller and it's generally a 1-to-1 mapping with the UI. It's just an alternative mechanism that solves the same problem (of abstraction and seperation of concerns) but better suited to the technology.
Theres some useful info here which might help understand the difference.
Best approach to use strongly typed views
Models:
public class ContentPage
{
public string Title { get; set; }
public string Description { get; set; }
}
public class ContentPagesModel
{
public ContentPage GetAboutPage()
{
var page = new ContentPage();
page.Title = "About us";
page.Description = "This page introduces us";
return page;
}
}
Controller:
public ActionResult About()
{
var model = new ContentPagesModel();
var page = model.GetAboutPage();
return View(page);
}
View:
#model Experiments.AspNetMvc3NewFeatures.Razor.Models.ContentPage
#{
View.Title = Model.Title;
}
<h2>About</h2>
<p>
#Model.Description
</p>
for more detail check out here
I case of using string as keys of ViewData - yes, it will be a lot of exceptions if someone will refactor code.
It sounds like your understanding of MVC is very old, probably based on MVC 1. Things have changed tremendously in the last couple of years.
Now we have strongly typed view models, and we have the ability to use expressions in the view, which by default aren't compile-time validated, but they can be for debug purposes (though it slows down the build a great deal).
What's more, we don't pass model data through ViewDate anymore (well, not directly anyways.. it's still passed that way but the framework hides it).

Using view models in ASP.NET MVC 3

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.

Categories