Building a complex Object step by step. Where to save it? - asp.net-mvc

I am using ASP.NET MVC. My requirement is to build a complex object (an object made of other object) through a step-by-step procedure like in a wizard.
Every dependent object shall be build on it's step and shall be validated in it's step. For example
public class ComplexObjectModel {
public Object1 MyObject1 { get; set; }
public Object2 MyObject1 { get; set; }
public Object3 MyObject1 { get; set; }
}
As there is no built-in facility for a wizard I have decided to create 3 model classes and 3 strong typed partial views binded to these models.
On every step of my pseudo wizard I validate the dependent model object and set the property of the complex object to its reference.
I was thinking to save the complex object inside the ViewData/TempData in the following way
In the controller action
[HttpPost]
public ActionResult MyAction1() {
ComplexObjectModel com = (ComplexObjectModel)ViewData["ComplexObjectModel"];
com.MyObject1 = new Object1();
ViewData["ComplexObjectModel"] = com;
return PartialView( "MyAction2", com.Object1 );
}
and in the View
<% using (Html.BeginForm()) { %>
<%= Html.Hidden("ComplexObjectModel", ViewData["ComplexObjectModel"]) %>
... view fields for Object1, Object n ....
<% } %>
But doing this way the object is not passed back-and-forth between the view and the controller and I have experienced that is null when it comes back from the view to the next action.
Is there a way to support this requirement?
thanks for helping

There are a couple of ways I might tackle this.
First; I might decide to store all this in the session object. I am assuming here that the models are quite large and so I wouldn't want them stored on the view and passed back each time I go to the next page.
Second; I might store them in the database and if the wizard didn't complete then delete them as part of a background process.
The one thing I wouldn't do is pass the complex object to each view. The view should really need to know anything about any other view in a restful world and so I'd be inclined not to do it.
Of course that does mean you need to decide a storage place for the data. If I had a Large objcect then I'd choose the database and if was fairly small then I'd choose the session object.
As you have already found, having all the data for each object in each view can be problematic.
However, if you are determined to do this the View way then here is what I'd do;
Create a partial view which deals
only with each object in the complex
model.
On each view, include all three, or
more, of the partial views.
For each partial view which is not
an active participant in the view,
place it within a div that is
hidden.
At least then when you change a property, or add one, you simply set it in the partial view once and not three times. Also if there is an error, you can unhide the divs and see if the data is coming in.
Also each field should then have the id of ModelName.Property so that the controller knows where the property is.
<%= Html.TextBox("MyObject1.MyProperty1", value) %>
Then in the controller you simply do, and this off the cuff;
[HttpPost]
public ActionResult MyAction1(ComplexObjectModel complexModel) {

You could take a look at MVC Futures Html.Serialize helper method which allows you to keep state into a hidden field between the controller actions in a similar way classic WebForms does it.

Related

How get the value of an object property that has been excluded from the bind

I have the following model:-
[MetadataType(typeof(TMSServer_Validation))]
[Bind(Exclude = "TMSRack,ServerModel")]
public partial class TMSServer
{
}
and I have the following drop down inside my view:-
#Html.DropDownListFor(model =>model.Server.TMSRack.DataCenterID, ((IEnumerable<TMS.Models.DataCenter>)ViewBag.DataCenters).Select(option => new SelectListItem {
Text = (option == null ? "None" : option.Name),
Value = option.ID.ToString(),
Selected = (Model.Server.TMSRack != null) && (option.ID == Model.Server.TMSRack.DataCenterID)
}), "Choose...")
Then on my controller class I have the following :-
ViewBag.Racks = repository.getrelatedracks(Server.TMSRack.DataCenterID);
But since I have excluded the TMSRack navigation property (mainly to avoid over-posting attacks), so the Server.TMSRack.DataCenterID will always be null. And to get its value I wrote the following:-
ViewBag.Racks = repository.getrelatedracks(Int32.Parse( Request.Form["Server.TMSRack.DataCenterID"]));
But I know that using Request.Form is not the right approach to follow, so my question is there a way to get the excluded property using more reliable way ?
Thanks
My answer is going to assume TMSServer is a domain model.
With that in mind, this is the perfect example of when to use a view model. By using a view model instead, you have complete control over how the properties are mapped from the view model to the domain model. Something like:
public class RackViewModel
{
public int DataCenterID
// other Rack properties
}
Then either send a list of RackViewModel to your view, or create a view model that encompasses all of that, too:
public class ContainerViewModel
{
public List<RackViewModel> Racks { get; set; }
// other view-specific properties
}
Now, when you POST the data back, not only do you have complete control over what properties you want to bind to your view models, you also have complete control over the mapping that takes place from converting your view models to domain models.
The bottom-line is this: if your view accepts a view model that only allows the user to POST the data they should be allowed to POST, over-posting doesn't even exist. Well-designed view models, or even making the distinction between a view model and an input model (i.e. a separate model that represents the data you want to bind back to in your action), eliminates over-posting entirely.
Over-posting only exists because you're not restricting the model binding process enough. If you ask it to bind to a class that has 10 properties in it when you only need 3 you're allowing the user to potentially stuff data into those other 7 properties.
This is one reason why view models are so popular. They allow you to narrow the scope of your view, whilst also narrowing the scope of the model binder. That leaves you free to properly manage the process of mapping from your view model to your domain model, without introducing a vulnerability.
Update
As you don't want to go the view model approach, your idea will work but you can do it slightly differently. Something along the lines of:
public ActionResult SomeAction(SomeModel model, TMSRack rack)
Where:
SomeModel is the type of model you're decorating with Bind(Exclude...) (it's not obvious what type that is from your question.
TMSRack is the type I assume you want to bind to.
As TMSRack is defined in your main model anyway, as long as you're using the Html.* helpers, it will have the correct names generated for it on the form in order to bind straight back to it as a separate parameter on your action. Then you can do whatever you want with it, without resorting to Request.Form.

ASP MVC 4 managing object state in controller

I am new to MVC and am having a conceptual problem with state and object persistence and hope someone can put my thoughts in order.
I have a remote webservice which provides methods to manage orders. An order consists of a header and Lines as you would expect. Lines can have additional requirements.
I have my domain objects created (using xsd2code from the webservice schema), the webservice calls and object serialization all working fine. I've build the DAL/BLL layers and it's all working - tested using a WinForms testbed app front-end.
I have view model objects mapped from the domain objects using Automapper. As the order is returned from a single webservice method complete with lines etc I have an OrderViewModel as follows
public class OrderViewModel
{
public OrderHeaderViewModel OrderHeader { set; get; }
public List<OrderLineViewModel> OrderLines { set; get; }
public List<OrderLineAdditionalViewModel> OrderLineAdditional { set; get; }
public List<OrderJustificationViewModel> OrderJustifications { set; get; }
}
Firstly I'm wondering if I should dispense with the OrderViewModel as if I pass this as a model to a view I'm passing far more data than I need. Views only need OrderHeader or OrderLines etc - not the entire order.
Now my conceptual problem is in the controllers and the views and object persistence.
My Order controller has a Detail Action which performs the load of the order from the webservice and maps the Domain object to the OrderViewModel object.
public ActionResult Details(string orderNumber)
{
OrderViewModel viewModel = new OrderViewModel();
var order = WebServiceAccess.LoadOrderByOrderNumber(orderNumber,"OBOS_WS");
viewModel = AutoMapper.Mapper.Map<BusinessEntities.Order, ViewModels.OrderViewModel>(order);
return View(viewModel);
}
But the Order/Details.cshtml just has the page layout and a call to two partial pages for the header and the lines (I swap the Headerview for a HeaderEdit using Ajax, same for the LinesView)
#{ Html.RenderPartial("DetailsHeaderViewPartial", Model);}
#{ Html.RenderPartial("DetailsLinesViewPartial", Model);}
At the moment I'm passing the model into the main Details container page, then into the RenderPartials, However I don't think that the model should be passed to the main Detail page, as it doesn't need it - the model is only needed in the DetailsHeaderViewPartial, DetailsLinesViewPartial so I'd be better off using #RenderAction here instead and passing the model into the Header/Lines views instead.
However, The Order is retrieved from the webservice in the ActionResult Details() how can I make the retrieved OrderViewModel object available in the ActionResult HeaderDetails() / LineDetails() methods of the controller to pass as the model in return PartialView(...,model) ?
Should I use a User Session to store the Order ViewModel so it can be used across actions in the controller.
Moving on from this stage the user will be able to maintain the order (add/remove lines - edit the header etc). As the webservice call to save the order could take a few seconds to complete I'd rather only call the save method when the user has finished with the order. I therefore would like to persist the in-progress order locally somewhere whilst it's being worked on. User session ?
Many thanks for any advice. Once I've got my head around state management for the ViewModels I'll be able to stop reading a million Blog posts and actually write this thing !
You actually have a few questions here so I will try to address them all the best I can.
1) Dispensing with the view model : I would say no. The view model represents the data that you need in order to populate your view. It seems like you are using the view model as an identical container to the domain model object. So you are asking if you should dispense with it and just pass the domain model to the view while your original concern is that you are passing along more data then you really need as is?
Rather then dispensing with the view model, I would revisit your properties on your view model. Only use properties that you need and create the mapping logic (either with automapper or on your own) for taking the complex domain object and populating the properties on the view model.
summation: build the view model to be only things that the view needs and write mapping logic to populate that view model.
2) This is just a statement of best practice before I breakdown your specific scenario.
You describe your architecture as having a BLL and DAL. If that is the case then you should not be persisting any objects from your controller. The controller should not have any knowledge of the database even existing and the objects used in the controller should have no idea of how to persist themselves. The objects that are going between your controller and the web service should strictly be Data Transfer Objects (DTO's). If you are unfamiliar with what constitutes a DTO then I highly suggest that you do some research and try to build them into your solution. It will help you conceptually see the difference between view model objects, domain objects and data transfer objects.
3) I would not try to store an order object in the session. I would re-analyze how you are breaking up the partial views within the view so that you can call actions with the ordersviewmodel being the parameter in a way that you need. It sounds like you are needlessly breaking up views into partial views.
4) You should not be concerned with state management for the view model object. Your view (which can be comprised of many partial views) is filled based on properties provided by the view model. The user can make changes using the UI you have developed. Since you express the desire to only save once they are finished making all changes to optimize calls to the web service, you just need to repopulate the fields of the view model upon clicking submit. Now you have a "state" for orderviewmodel that represents the users changes. You can send this object to the web service after converting back to a DTO (if you do what I said above) or by mapping it to the domain object.
1 final note. You are using automapper to map your domain to the view model. I am assuming that your view model is too complex and includes things that you don't need because you built your view model to emulate the domain object so that automapper could map by naming convention. Automapper has an api for doing complex (custom) mappings that fall outside of standard same name properties. Don't let automapper constrain you to building your view models a certain way.
Hope this helps

Adding a Parent's ID to hidden field

I'm struggling, a bit, with MVC / Razor / Entity Framework.
I need to create an object which references a parent object. I have a field in my model, called ParentID, but I'm having trouble figuring out how to populate it with the parent's ID.
I'm thinking I need a hidden-field in my view, and then maybe place the ParentID in the ViewBag, and point that ViewBag property to the hidden field, but I can't seem to get that to work.
Something like this, was my assumption:
#Html.Hidden("BladeID", ViewBag.ParentBlade)
I'm not sure I've explained myself very well, so please ask away, and I'll expand.
Also, I'm not sure I'm doing this the correct way. This is all very new to me, coming from webforms.
If the ParentID is already in the model, why not populate the hidden field directly from there?
#Html.Hidden("BladeID", Model.ParentID)
If you really want to populate it from the ViewBag you'll have tot cast it back to int (assuming that's the type of ParentID), because the ViewBag is of type dynamic:
#Html.Hidden("BladeID", (int)ViewBag.ParentBlade)
UPDATE based on comments
If your view depends on two (or more) separate model classes you can always opt for creating custom a view model for that view, something like:
public class ParentChildViewModel
{
public Blade Parent { get; set; }
public Blade Child { get; set; }
}
and then use that class as the model in your view:
#model ParentChildViewModel
// more view code here
#Html.Hidden("BladeID", Model.Child.ParentID)
That said, there is, in my opinion, nothing wrong in using the ViewBag in this case, particularly if all you need is one property.

Can you remove the HTML Field Prefix from strongly typed models in MVC 3?

I have a view model like this:
public class EditVM
{
public Media.Domain.Entities.Movie Movie { get; set; }
public IEnumerable<Genre> Genres { get; set; }
}
Movie is the real entity I wish to edit. Genres is simply present to populate a drop down. I would prefer that when I call:
#Html.TextBoxFor(m => m.Movie.Title)
inside my strongly typed view that the input control have a name = "Title" instead of "Movie.Title"
I do not wish to split my view into partial views or lose my strongly typed view by using ViewData or the like.
Is there a way to express to the View that I do not wish to have the Movie. prefix? I noticed that you can set:
ViewData.TemplateInfo.HtmlFieldPrefix = "x";
in the controller, but unfortunately it seems only to allow adding an additional prefix. Setting it to "" does nothing.
Is there any work around for this? Or am I stuck with the unfortunate prefix that isn't really necessary in this case if I wish to keep strongly typed views and lambdas?
Thanks for any help.
Update:
Here's the controller actions to maybe make things a bit clearer.
public ActionResult Edit(int? id)
{
var vm = new EditVM
{
Movie = id.HasValue ? _movieSvc.Find(id.Value) : new Movie(),
Genres = AppData.ListGenres()
};
return View(vm);
}
[HttpPost]
public void Edit([Bind(Prefix = "Movie")]Movie m)
{
_movieSvc.AddOrUpdateMovie(m); //Exceptions handled elsewhere
}
No, in order to do what you want you would have to rewrite the Html helpers, and then you would have to write your own model binder. Seems like a lot of work for minimal gain.
The only choice is a Partial view in which you pass the Movie object as the model. However, this would require you to write your own model binder to have it be recognized.
The reason you have to do m.Movie.Title is so that the ID has the correct name, so the model binder can recognize it as a member of your model.
Based on your update:
Your options are:
Use non-strongly typed helpers.
Use a partial view.
Rewrite the stronly typed helpers
Don't use the helpers at all, and write the values to the HTML
Personally, i'd just use 1 or 2, probably 2.
EDIT:
Based on your update above. Change your code to this (note, Genres does not get posted back to the server, so m.Genres will just be null on postback):
[HttpPost]
public void Edit(EditVM m)
{
_movieSvc.AddOrUpdateMovie(m.Movie); //Exceptions handled elsewhere
}
EDIT:
I did just think of an alternative to this. You could simply do this:
#{ var Movie = Model.Movie; }
#Html.TextBoxFor(m => Movie.Title)
However, if there was a validation error, you would have to recreate your EditVM.
I have a view model like this
I think that you might have some misunderstanding about what a view model is. A view model shouldn't contain any reference to your domain models which is what those Movie and Genre classes seem to be. I mean creating a new class that you suffix with VM and in which you stuff all your domain models as properties is not really a view model. A view model is a class that is specifically designed to meet the requirements of your view.
A much more correct view model would looks like this:
public class EditVM
{
public string MovieTitle { get; set; }
public IEnumerable<GenreViewModel> Genres { get; set; }
}
and in your view you would have:
#Html.EditorFor(x => x.MovieTitle)
#Html.EditorFor(x => x.Genres)
Another option is to either use the TextBox(string name, object value) overload instead of the TextBoxFor:
#Html.TextBox("Title", Model.Movie.Title)
You could also specify the input tag HTML instead of using a helper.
Another option is to take EditVM as your postback parameter. This is what I would do. My post action parameter is always the same type of the .cshtml model. Yes there will be properties like lists that are null, but you just ignore those. It also allows you to gracefully handle post errors as well because if there is an error you'll need to return an instance of that view model anyhow, and have the values they submitted included. I usually have private methods or DB layer that handles retrieving the various lists that go into the ViewModel, since those will be empty on postback and will need to be repopulated, while not touching the properties that were in the post.
With your post method as it is now, if you need to return the same view, you've gotta create a new EditVM and then copy any posted values into it, and still populate the lists. With my method, you eliminate one of those mapping steps. If you are posting more than one thing, are you going to have umpteen different parameters on your post action? Just let them all come naturally into a single parameter typed to the EditVM of the View. While maybe having those null properties in the VM during the postback feels icky, you get a nice predictable consistency between View and postback IMO. You don't have to spend alot of time thinking about what combination of parameters on your post method will get you all the pieces of data from the form.

Storing state in asp.net MVC

I'm building an asp.net MVC 2 app.
I have a list view which lists items based on a parameter. In the database I have a parent and child table, so my list view lists all the child records for where the parent's id matches the value specified in the parameter.
This is my controller and model:
public ActionResult List(int ParentID)
{
return View(new Models.ChildListModel(ParentID));
}
public class ChildListModel
{
public int ParentID {get;set;}
public ManagementUserListModel(int iParentID)
{
this.ParentID = iParentID;
this.Children = DataAccessLayer.ListChildrenForParent(iParentID);
}
public List<Child> Children {get;set;}
}
I also have a details and create action for that controller. The details and create view have a "back to list" action, which I want to go back to the list view, and maintain the original ParentID. So far I've been doing this by creating a hidden field called ParentID in the list, edit, create and details views, so that the model's ParentID property will get populated correctly:
<%= Html.HiddenFor(model => model.ParentID) %>
Then in the "Back to List" action in each view I pass the ParentID:
<%=Html.ActionLink("Back to List", "List", new {ParentID = Model.ParentID}) %>
This all works, but I'm not a big fan of storing raw IDs in the html. Are there any better ways to do this? Is there some built in way to encrypt the data (kind of like the standard asp.net viewstate did?) I'm just trying to achieve some sort of tamper resistance, and trying to avoid using session state (TempData, etc) because I don't want to have to handle session timeouts.
You may take a look at this article. You could use the new Html.Serialize extension method in your view which allows you to serialize entire objects and encrypt it:
<%= Html.Serialize("person", Model, SerializationMode.Encrypted) %>
Which serializes the Model into a hidden field and encrypts the value. To get the model back you use the DeserializeAttribute in the controller action to which the form is submitted:
public ActionResult Edit([Deserialize]Person person) { }
Another way to do this that doesn't expose the id and also doesn't add to the weight of the subsequent form post (like Webform's ViewState does and Html.Serialize would) is to use the session as the backing store inside your Model class.
public class ChildListModel
{
public int ParentID {
get
{
return (int)HttpContext.Current.Session["ChildListModel.ParentID"];
}
set
{
HttpContext.Current.Session["ChildListModel.ParentID"] = value;
}
}
public ManagementUserListModel(int iParentID)
{
this.ParentID = iParentID;
this.Children = DataAccessLayer.ListChildrenForParent(iParentID);
}
public List<Child> Children {get;set;}
}
If you like, you could even store the entire Parent object in your model that way instead of just it's ID - that would add to the session size on the server which may or may not be desirable (depending on how long your session lasts and whether it is set to be stored in memory or in sql server, etc.)
The easiest way would be to keep the parentid in the URL. It will look a bit strange for the Create Action but I still think that this the less troublesome way.
If you keep state, you will alwas have the problem that you can not hit F5 and you can not bookmark the page.
The backlink is a simple ActionLink in this case.
Urls would be:
/YourController/List/YourParentParameterValue
/YourController/Detail/YourParentParameterValue/YourDetailParameterValue
/YourController/Create/YourParentParameterValue

Resources