I'm working on a MVC 5 application.
I've got an action taking a complex object as a parameter. This action generates a view, and this view calls another action, that needs this complex object to be passed to it.
What's the bast way to pass it along, avoiding storing it in the session (I know, that would be the simplest to do, but one of the project requirements is to avoid using session except for security things) ?
Example controller:
public class TestController {
public ActionResult Action1(ComplexObject object)
{
TestModel model = new TestModel
{
model.ComplexObject = complexObject
};
return View(model);
}
[HttpPost]
public ActionResult Action2(TestModel model)
{
DoSomeStuff(model.ComplexObject);
return View(model);
}
}
So the problem is to store this complex object somewhere (again, not in session) to be able to retrieve it in the Action2 the way it existed in Action1. I currently add it to the model, but it's not mandatory, as it is not used in the view.
I thought of creating hidden fields in the view, but storing everything from the object is a real pain in the ass, it's ugly, and would introduce errors if the object structure changes in the future.
Is there a good practice to do something like this ? Or is there a elegant way to somehow serialize and deserialize it ? Any idea welcome.
If your TestModel definition has a ComplexObject (which I imagine it does because you are assigning the ComplexObject value to it), when you pass that model to the view, as you are doing in Action1 your view will receive it as long as you have strongly typed your view as such:
#model = TestModel //simplified path since I do not know the full path of your model
Your Action2 method is an HttpPost which makes me conclude that you will have a user post the form on the view. When the form is posted, your strongly typed view will pass the model into your Action2 method. If you use any HTML helpers to modify any of the properties of TestModel, then those changes will be passed to your Action2 method as well.
Related
I noticed almost all code I found just return View(); in [HttpGet] Create action method. I wonder why don't we need to return View(new Person()) for example?
Most of the default constructs you use in a razor view template (like Html.EditorFor, Html.LabelFor, etc) work when the model is null. They can access the meta data of the class type of the model for tasks like displaying the label or deciding on the required format of the value. And they just use an empty value (empty string, 0, false) for the value when the model instance is null.
So, the code you see (View called without model instance) will only run into trouble if there is code in the view template that manually attempts to access the model instance without checking whether it is null.
Still, in my opinion it is best practice to pass a new instance of the Model or ViewModel type when calling a view to create a new object. This way, default values set in the constructor of the Model or ViewModel or values set in the controller action before will not be lost and used in the view. Also, there will not be a problem when someone modifying the view template decides to access the model instance without null checks.
I'm not sure what code you're looking at, but if I'm returning a view that requires a viewmodel, I will pretty much always include the viewmodel object when I call the view.
public ActionResult ViewTime(int id, DateTime? from, DateTime? to)
{
var viewTimeModel = _repository.ViewTime_Read(User, from, to, id);
return View(viewTimeModel);
}
It's possible that the view has some dynamic controls, like grids, etc in it that are responsible for getting their own data, perhaps via javascript calls to the controller methods for json. In that case, the view is pretty much a "dumb shell" and the controls on it are doing the heavy lifting.
I am having a hard time understanding the way MVC behaves. In my Controller class I created a model and initialized it the main ActionMethod. After that I call another controller method and it turns out that the model is null. Why is that?
Now it seems I can only use them once for passing database info to the views. Is it necessary to always modify/query the database? I know in most cases it makes sense to do it, but I would like to keep these parts separate.
Edit:
Here's some code:
public class TestController : Controller {
TestModel model;
public ActionResult Index() {
model = new TestModel();
return View(model);
}
public ActionResult OtherMethod {
// Here I would like to access/modify the previously created model, but it is null
return View();
}
}
You should use Html helpers to bind properties of your model in view
or simply use #Html.EditorForModel() helper. It will create UI and binding for entire model
the default lifetime of controller is "per request" this means that a new instance of your TestController is created on every HttpRequest.
you could change this behavior by creating a custom controller factory but i would not recommend that, i suggest u use the TempData collection instead.
see http://msdn.microsoft.com/en-us/library/dd394711(v=vs.100).aspx
I am reading a book on ASP.NET MVC and I'm wondering how the following example works:
Example #1
Controller
public class MyController : Controller
{
public ActionResult Index()
{
ViewBag.MyProperty = 5;
return View();
}
}
View
<h1>#ViewBag.MyProperty</h1>
Now I understand that ViewBag is a dynamic object, so that's how you can set the property (though I don't know much about dynamic objects, never worked with them.) But how does the view get the specific instance of the ViewBag from the controller, even though we don't pass anything directly?
I thought that the ViewBag could be a public static object, but then any change to it would be global and it wouldn't be specific to a view instance.
Could you elaborate as to how this works behind the scenes?
Example #2
Controller
public class MyController : Controller
{
public ActionResult Index()
{
ViewBag.MyProperty = 5;
return View();
}
public ActionResult Index2()
{
ViewBag.MyProperty = 6;
return View();
}
}
Now let's say the Index method is called first, and then the Index2. In the end the value of ViewBag.MyProperty will end up as 6 (the value from Index2). I feel that it is not a good thing to do, but at the same time I feel that I'm thinking in desktop development terms. Maybe it doesn't matter when used with ASP.NET MVC, as the web is stateless. Is this the case?
ViewBag is a property of ControllerBase, which all controllers must inherit from. It's a dynamic object, that's why you can add new properties to it without getting compile time errors.
It's not static, it's a member of the object. During the request lifetime, the controller instance is created and disposed, so you won't have "concurrency" problems, like overwriting the value.
The View (and its variants) method is not static as well, and this is how the view receives the ViewBag values: during the process of rendering the view, the controller instance has its ViewBag instance as well.
If you would analyse ControllerBase class you would see that ViewBag property is a "proxy" to ViewData property just to make your source look nicer. (I even remember Scott Hanselman taking interview from Phil Haack where Phil introduced ViewBag property as a shortcut to ViewData and eliminating the need of repeated square brackets and quotes). Even though ViewBag property is exposed as dynamic object it implements a DynamicViewDataDictionary class which works directly with ViewData.
Looking at source code of Controller class you can find this method:
protected internal virtual ViewResult View(string viewName, string masterName, object model)
So basically when you call return View(); from your controller it creates a new instance of ActionResult class passing ViewData from controller to it's constructor. Instance of ActionResult is then passed to a particular view engine (ASPX, Razor) so it could be used to render a view in question.
Making ViewBag/ViewData public static could be harmful. Each web request to your MVC application creates a new instance of controller. If you'd have ViewData/ViewBag as public static then two concurrent users would share same data from ViewBag/ViewData.
Here is a video. Discussion on ViewBag (formder ViewModel) starts at 04:05
ViewBag is a property of ControllerBase. It is defined as follows:
public Object ViewBag { get; }
Note that this signature is actually incorrect. Here's what the source code actually looks like:
public dynamic ViewBag {
get {
if (_dynamicViewDataDictionary == null) {
_dynamicViewDataDictionary = new DynamicViewDataDictionary(() => ViewData);
}
return _dynamicViewDataDictionary;
}
}
_dynamicViewDataDictionary is an ExpandoObject; you can add properties to it at runtime. Its lifetime is the same as that of the controller, which is the lifetime of the HTTP request.
In my Controller I have a ProductInfo class from my Domain Model and I need some of its information to populate my View Model ProductStatsVM.
How do you populate the View Model? I heard three possible ways:
Populate the View Model directly from the Controller (not good, I want to keep my Controller slim)
By using a View Model constructor and pass the domain model as parameter. (I have to create a constructor for each domain model class I want to use)
By using a Fill() method. (I saw it on the web, no idea how it works I guess this way the ViewModel should be aware of the Service Layer and creates coupling).
I know there are tools like AutoMapper, which I am going to use indeed, but before I want to understand the logic on how to fill a View Model from the Controller without using any additional tool.
The idea is that your controller action queries some repository to fetch a domain model. Then it passes this domain model to a mapping layer which is responsible to convert it to a view model and finally it passes the view model to the view:
public ActionResult Index(int id)
{
ProductInfo product = repository.GetProductInfo(id);
ProductViewModel viewModel = Mapper.Map<ProductInfo, ProductViewModel>(product);
return View(viewModel);
}
and you could even make your controller slimmer by introducing a custom action filter that will automatically intercept the Model in the OnActionExecuted event and call into the mapping layer to substitute it with the corresponding view model so that your controller action now becomes:
[AutoMapTo(typeof(ProductViewModel))]
public ActionResult Index(int id)
{
ProductInfo product = repository.GetProductInfo(id);
return View(product);
}
and of course now the view is strongly typed to ProductViewModel:
#model ProductViewModel
...
Up to you to implement the Mapper.Map<TSource, TDest> method. And if you don't want to implement it yourself you could download AutoMapper which already has this method for you.
The mapping layer is something that is part of the MVC application. It must be aware of both the domain models coming from your service layer and the view models defined in your MVC application in order to be able to perform the mapping.
Don't use constructors (other than the default parameterless one) in your view models. The default model binder will choke if the view model doesn't have a parameterless constructor in your POST actions and you will have to implement custom model binders.
Since viewmodels are needed to populate UI, it should be good idea to get them populated via controllers. You still may keep them slim by using Automapper.
So I've return View(object);'d, and I press submit on that page, how do I get that object back in the controller? any ideas?
my viewpage is
public partial class Index : ViewPage<List<Models.Pricing>>
and
public ActionResult Index(int id, FormCollection datesForm,
[Bind(Prefix="")]List<Pricing> model)
{
return View("Index", new{id});
}
Because you are really trying to retrieve a list of models (of type Pricing), you will either need to develop a custom IModelBinder and use it or iterate through the form collection and pull the data for each pricing model out of the form parameters and reconstitute them. Given your code, though, I don't see why you need to do this.
Is it really the case that you want to get the model data associated with the given id? Or is there more code than what you've shown? In the former case, the best thing to do is probably re-run the query using the id and not bother with the extra parameters to the controller action.