MVC3 Model binding in HTTP GET request? - c#

Without customization, can I do something like this in MVC 3?
[HttpGet]
public ViewResult MyAction(ViewModel model)
{
// Do stuff
return View("ViewName", model);
}
The reason I am doing this is to pass data between different pages as part of a work flow. (I.e. when user fnishes what's needed in step 1, pass the form data to step 2...)

It will work as long as you have the same parameter Name as of the Property name of your Model class
Assuming your class is like this
public class ViewModel
{
public string Name { set;get;}
public string Loc{ set;get;}
}
You can do a Get request like this
MyAction?Name=jon&Loc=America

Shyju's answer only works if the members of class in the endpoint's method signature contains only scalar properties. But what if you have nested classes? Let's assume that your ViewModel class looks like this:
public class ViewModel
{
public string Name { get; set; }
public string Title { get; set; }
public Address MyAddress { get; set; }
}
And the Address class looks like this:
public class Address
{
public string Line1 { get; set; }
public string Line2 { get; set; }
}
Now let's say the GET request was done via AJAX and you did something like this in JavaScript:
var address = {
Line1: "123 Nowhere St.",
Line2: "Apt. B5"
}
var getRequestData = {
Name: "Joe",
Title: "Manager",
MyAddress: address
}
var uriString = $.param(getRequestData); //the parameters for the GET request
$.get("/ViewResult?" + uriString, function (data) { /*callback function*/ });
Even though the shape of your address object in JavaScript perfectly matches the C# Address class in the endpoint's method signature, the Line1 and Line2 sub-properties will NOT bind. Their values will come through as null.
There are two workarounds to this.
Workaround 1:
The first is to use dot notation when naming the parameters in the GET request instead of nested JavaScript objects. Using this method, the GET request data in AJAX would look like this:
var getRequestData = {
Name: "Joe",
Title: "Manager",
MyAddress.Line1: "123 Nowhere St.",
MyAddress.Line2: "Apt. B5"
}
MVC model binding will know how to do this, as long as all your property names all match up (they are case-sensitive, so be careful).
If you're not using AJAX, but just a plain HTML form submit, it's even easier. Just name the input elements with that same dot notation. Razor syntax makes this really easy with helper methods like TextBoxFor(), but here's an example in plain HTML:
<form method="get" action="/ViewResult">
<input type="text" name="Name" />
<input type="text" name="Title" />
<input type="text" name="MyAddress.Line1" />
<input type="text" name="MyAddress.Line2" />
<button type="submit">Submit GET request</button>
</form>
Workaround 2:
The other way around this is to simply use a POST request instead of a GET. Beware that it's technically bad practice to perform a POST request without the intent of actually changing some data on the server side, but it is an option.

You can do it; it will automatically bind any values in the query string to properties with matching names.
That said, it's not something that's generally done; it's the [HttpPost] method where you see the model binding performed, as the interfaces for the two actions need to be different somehow. You can solve that by posting back to a different action name, but you may still trigger model validation errors on the (partial) load of the model, which would be really confusing to a user.

For Web API 2:
[HttpGet]
public ActionResult Get([FromUri]ViewModel model)
{
// Do stuff
return View("ViewName", model);
}

You can post a form to a get by setting the PostMethod attribute to get. If the form's input fields match any of the accepting ViewModel then they will be filled. These matches are determined by the name field in an input (<input name="MatchedField"> -> public string MatchedField { get; set; }).
What you should do is pass the form from a post, and then redirect to the get from the post action. This pattern is best practice and is known as the Post-Redirect-Get pattern.

I would advise against this approach. Best solution to just use POST, because if you use GET, once you click back from step 3 to step 2 and the browser cache is not available, you will perform actions on an old version of the ViewModel. Is there a particular reason why you want to use GET?

I can not suggest to use QueryString to pass values.
You can use one of below:
This code will render a partial view with the given model.Be sure you add model to your view. And your view should be placed in Shared folder
public ActionResult myaction(ViewModel model)
{
return PartialView("anotherView", model);
}
Another way to do almost the same thing:
public ActionResult myaction(ViewModel model)
{
return View("someAnotherView", model);
}
if your view is not in the same controller , use the path for view name like "../Controller/viewName"
There is also a different approach which can be done by using TempData:
public ActionResult myaction(ViewModel model)
{
TempData["model"] = model;
return RedirectToAction("someAnotherView");
}
but you should reach your data in the view with the code as shown below:
#{
ViewModel model=(ViewModel)TempData["model"];
}
Hope one of above helps..
Regards

Related

ASP.NET MVC - Proper usage of View Model and Command pattern

I've been writing ASP.NET MVC applications for some time and I found them to be a good place for using the command pattern: we represent every user request as a command - a set of input params - then this command is processed (processing includes validation and other domain logic) and the result is sent back to the user.
Another thing I've been using in my applications is view models. I found them to be a more convenient way of passing data to the view than using domain objects as models or filling ViewData/ViewBag.
These 2 concepts work great for separating data that is shown to the user from user input and its handling, but they don't quite agree with each other in ASP.NET MVC.
Let's say I want to use commands and view models when developing a simple web store where users look through products and can order a product by providing their name and email address:
class ProductViewModel
{
public ProductViewModel(int id) { /* init */ }
public int Id { get; set; }
public string Name { get; set; }
// a LOT of other properties (let's say 50)
}
class OrderProductCommand
{
public int ProductId { get; set; }
[Required(ErrorMessage = "Name not specified")]
public string Name { get; set; }
[Required(ErrorMessage ="E-Mail not specified")]
public string Email { get; set; }
public CommandResult Process() { /* validate, save to DB, send email, etc. */ }
}
When looking through tutorials and SO I've seen people suggest several ways of doing this.
Option 1
Controller:
[HttpGet]
public ActionResult Product(int id)
{
return View(new ProductViewModel(id));
}
[HttpPost]
public ActionResult Product(OrderProductCommand command)
{
if (ModelState.IsValid)
{
var result = command.Process();
if(result.Success)
return View("ThankYou");
else
result.CopyErrorsToModelState(ModelState);
}
return Product(command.Id);
}
View:
#using (Html.BeginForm())
{
#Html.Hidden("ProductId", Model.Id)
#Html.TextBox("Name")
#Html.TextBox("Email")
<input type="submit" value="Place order" />
}
Pros: view model and command are separated from each other, the HttpPost method looks clean
Cons: I can't use convenient HTML helpers like #Html.TextBoxFor(model => model.Email), I can't use client validation (see my other question)
Option 2
We copy Id, Name and Email together with their validation attributes from command to viewModel.
Controller:
[HttpPost]
public ActionResult Product(ProductViewModel viewModel)
{
var command = new OrderProductCommand();
command.Id = viewModel.Id;
command.Name = viewModel.Name;
command.Email = viewModel.Email;
if (ModelState.IsValid)
// ...
}
View:
#Html.TextBoxFor(m => m.Email)
...
Pros: all of option 1 cons go away
Cons: copying of properties seems inconvenient (what if I have 50 of them?), validation of Name and Email in view model (it should be done in command where the rest of the domain logic resides), model as a POST parameter (see below)
Option 3
We make command a property of viewModel.
Controller:
[HttpPost]
public ActionResult Product(ProductViewModel viewModel)
{
var command = viewModel.Command;
if (ModelState.IsValid)
// ...
}
View:
#Html.TextBoxFor(m => m.Command.Email)
...
Pros: all of option 1 cons go away
Cons: view model should only contain data that is displayed to the user (and command is not displayed), model as POST parameter (see below)
--
What I don't like about options 2 and 3 is that we use a view model as a POST method parameter. This method is meant for handling user input (only 2 fields + 1 hidden in this case) and the model contains 50 more properties that I'll never use in this method and that will always be empty. Not to mention the necessity to create an empty constructor for the view model just to handle this POST request and the unnecessary memory consumption when creating large view model objects for every POST request.
My question is (that's like the longest question ever, I know): is there a secret Option 4 for properly using commands and view models that has all of the pros and none of the cons of the other ones? Or am I being paranoid and these cons are not that important and can be ignored?
Seems like the only other decent way go is to use a partial view for rendering the form and use OrderProductCommand as the view model.
Product.cshtml:
#model ProductViewModel
...
#Html.Partial("Product_OrderForm", new OrderProductCommand { ProductId = Model.Id })
...
Product_OrderForm.cshtml:
#model OrderProductCommand
...
#using (Html.BeginForm("Product", "Home"))
{
#Html.HiddenFor(cmd => cmd.ProductId)
#Html.TextBoxFor(cmd => cmd.Name)
#Html.TextBoxFor(cmd => cmd.Email)
<input type="submit" value="Place order" />
}
...
This way there is no need to create a data map between view models and business objects, and the controller code can be left clean as it was in in Option 1:
[HttpGet]
public ActionResult Product(int id)
{
return View(new ProductViewModel(id));
}
[HttpPost]
public ActionResult Product(OrderProductCommand command)
{
// process command...
}
Personally,
If I had to pass my model back to the view using a viewModel, I'd use option 4, inherit my view model from my command.
That way I get all the properties for my command, and I can set new properties that are just needed for the view, say dropdown list options etc.
Let inheritance do the work for you.
Also, you don't need to copy properties, in your post, don't send back a ViewModel, send back the command.
public ActionResult Product(PreOrderProductCommand command)
Don't forget, Mvc doesn't care what model is in your view, it only maps keys on the formcollection to properties in the model in the argument list. So even though you send a ProductViewModel out, you can still get a PreOrderProductCommand in.
HTH
Here's my take on the issue.
The reason we introduce all these layers (entity, view model, command etc.), which essentially represent the same concept within different domains, is to enforce separation of concerns.
However, as each layer is introduced, we increase the complexity and margin for error, due to increased mapping between objects and distributed validation.
In my opinion it is absolutely correct that entities and view models are implemented separately; a domain entity should represent the business logic, and shouldn't be polluted with UI specific features. Similarly, a view model shouldn't contain more than is necessary than to satisfy a specific view.
There is no reason, on the other hand, that commands should introduce a new layer to your architecture. A command simply needs to provide data, doesn't need to rely on a specific implementation, and therefore can be defined as an interface:
interface IOrderProductCommand
{
int ProductId { get; }
string Name { get; }
string Email { get; }
}
Whilst a view model isn't a command, or vice-versa, a view model could act as a command:
class ProductViewModel : IOrderProductCommand
{
public int ProductId { get; set; }
[Required(ErrorMessage = "Name not specified")]
public string Name { get; set; }
[Required(ErrorMessage ="E-Mail not specified")]
public string Email { get; set; }
public ProductViewModel(int id) { /* init */ }
// a LOT of other properties (let's say 50)
}
This way, the validation takes place only in the view model, which I think is the correct place for that to happen, as feedback can be given to the user immediately.
The command should simply transfer data, and the domain entity it mutates should validate itself anyway; a third layer of validation is unnecessary.
Your controller would then look as follows:
readonly CommandHandler _handler;
public YourController(CommandHandler handler)
{
_handler = handler;
}
[HttpGet]
public ActionResult Product(int id)
{
return View(new ProductViewModel(id));
}
[HttpPost]
public ActionResult Product(ProductViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
_handler.HandleProductCommand(model);
return RedirectToAction(nameof(Product), new { id = model.ProductId });
}
And the handler:
class CommandHandler
{
void HandleProductCommand(IOrderProductCommand command)
{
// Update domain...
}
// Other command handling methods...
}

Is passing the Viewmodel to the Controller a good idea?

I am fairly new to MVC and had a question about a form I am creating. The page has a form at the top and a grid at the bottom. As people enter data into the form and click the button, the form data is added to the grid below.
My plan is to use a BeginForm and send the form to an HttpPost controller method for processing and then bounce back to the view. Currently, I am using this for the form on the view:
#using (Html.BeginForm("AddRefund", "Refund", FormMethod.Post))
In the controller, I have this:
[HttpPost]
public ActionResult AddRefund(RefundModel refund)
{
if (ModelState.IsValid)
{
(etc...)
My problem is that the "refund" object in controller always arrives from the view empty. From my research, it seems that the model reference in the controller is just there to provide model structure, and NOT to receive the actual model from the view. I don't understand why this is, however, as it would seem very valuable to be able to send a populated viewmodel from the view to a controller.
Also, how would you guys handle the code for this problem? How would you collect all of these form submissions from the user, present them to the user in the grid below the form, and then ultimately submit the page and insert all of the items in the grid into the database?
edit: here is my view
#model RefundsProject.Models.RefundModel
#using (Html.BeginForm("AddRefund", "Refund", FormMethod.Post))
{
(all of the form elements are here)
<input id="button-add" type="submit" value=" Add Refund to List " />
}
Eventually, there will be another button at the very bottom of the view that will submit all of the items the user entered into the grid to the database.
From my research, it seems that the model reference in the controller is just there to provide model structure, and NOT to receive the actual model from the view.
This is completely the opposite of the way ASP.Net MVC was designed. ASP.Net comes with default ModelBinders that are used to Bind data from a Form, Querystring, Ajax (Json and XML) to a strongly typed object for a Controller Method.
My problem is that the "refund" object in controller always arrives from the view empty.
This is most likely due to a lack of knowledge or a misunderstand of how model binders work.
Also, how would you guys handle the code for this problem?
I would Ajax Post the RefundModel back to the controller to validate the refund. If it is valid, then dynamically create fields in the form that will eventually model bind back to an IEnumerable/List on a new method that will then verify all the refunds, one at a time (to validate the data again).
Here is an Extremely broken down example (probably needs some work, but the important parts are there):
Classes:
public class AddRefundsViewModel
{
public RefundModel Refund { get; set; }
}
public class RefundModel
{
public string Reason { get; set; }
public Decimal Amount { get; set; }
}
Methods:
public ActionResult AddRefunds()
{
var model = new AddRefundsViewModel()
model.Refund = new RefundModel();
return this.View(model);
}
[HttpPost]
public ActionResult ValidateRefund(AddRefundsViewModel model)
{
var result = new { isValid = modelState.IsValid };
return this.Json(result);
}
[HttpPost]
public ActionResult ValidateRefunds(IEnumerable<RefundModel> model)
{
var isRefundsValid = true;
foreach (var refund in model)
{
isRefundsValid = TryValidateModel(refund);
if (!isRefundsValid )
break;
}
if (isRefundsValid)
{
}
else
{
// either someone hacked the form or
// logic for refunds changed.
}
}
Views:
#model AddRefundsViewModel
// assuming RefundController
#using (Html.BeginForm("Refund", "ValidateRefunds", FormMethod.Post))
{
#html.EditFor(m => m.Refund.Reason)
#html.EditFor(m => m.Refund.Amount)
<input type="button" id="addRefundButton" name="addRefundButton" value="add"/>
<input type="submit" id="submitRefundButton" name="submitRefundButton" value="submit all"/>
}
<!-- jquery -->
$(document).ready(function()
{
$('#addRefundButton').on('click', function()
{
$.ajax({
url: '/Refund/ValidateRefund',
data: $("addRefundForm").serialize(),
success: function(result)
{
if (result.isValid)
{
// create new hidden imput elements, and grid
$("addRefundForm")[0].reset();
}
else
{
// Refund isn't valid
}
}
});
});
});
From my research, it seems that the model reference in the controller is just there to provide model structure, and NOT to receive the actual model from the view. I don't understand why this is, however, as it would seem very valuable to be able to send a populated viewmodel from the view to a controller.
Your a bit wrong. There is a difference between ViewModel and Domain Model. View Model is a class that you use to process the logic between views and your domain (business).
Then there is Domain Model (in .net) this is usually some data container objects (POCO). This is anemic. Based on DDD there is a little difference.
So what is the best practive?
It is always good to use a ViewModel object to transfer data between your views and controller.
Then in controller you can use a mapper (automapper or valueinjecter) to transform them.
Now you have your domain object that you can process.
Using ViewModels to pass data both up and down between controllers and views is completely acceptable.
To help with your model coming up empty issue, inputs, such as <input id="FirstName" type="text" /> need to have name attributes for the MVC model binder to map what you posted into your RefundModel object. In your View code you shared, you only showed a submit button, so it is unclear if your other elements you expect to get mapped have names or not.
To fix my above example of an input tag, you would do <input id="FirstName" name="FirstName" type="text" /> or use a Razor helper: #Html.TextBoxFor(m => m.FirstName)

How to post a c# object to an action from a view?

I have a view which is rendered by calling an action and I'm passing a view model to it e.g. Vm1 then populating some drop down lists.
On this view, I have a "Filters" section with some text boxes and a "Filter" button and I'd like to call another action passing the values of the text boxes and then rendering the second view partially on the page within a div.
So I have done this and my action looks like below which is called by ajax when the "Filter" button is clicked:
ActionResult ActionName (string inputText1, string inputText2, string inputText3, ...)
Because I have about 10 text boxes, I'd like to create a new c# object and passing that object to this action to look like this which is simpler:
ActionResult ActionName(MyActionFilters myFilters)
How to achieve this?
you can have a model as below
public class MyActionFilters
{
public string Property1{get;set;}
public string Property2{get;set;}
public string[] Property3{get;set;}
// add any number of properties....
}
you can pass the empty model in Get Action method
ActionResult ActionName()
{
MyActionFilters myEmptyActionFilers= new MyActionFilters();
View(myEmptyActionFilers)
}
in the form
Html.TextBoxFor(model => model.Property1)
Html.TextBoxFor(model => model.Property2)
then in the post method you can access the model that is populated in the form
I have removed the previous code. the new code is after the Edit Tag :)
Edit:
Sorry I was not around. This kind of functionality can be achieved easily using AJAX :)
It goes as below.
[HttpPost]
PartialViewResult ActionName(MyActionFilters myActionFilers)// this is magic
{
/*you can access the properties here like myActionFilers.Property1 and pass the
same object after any manipulation. Or if you decided have a model which contains
a variable to hold the search results as well. That is good.*/
return PartialView(myActionFilers);
}
So far this is a good example to refer.
And do not forget to add jquery.unobtrusive-ajax.js script reference to your view. If not Ajax will not affect.In the given example he has done it in the _Layout as you can see.
PS: Choose properties of models that is going to be passed to views, wisely and Enjoy Ajax!!
You need to set the form input names to the properties of your ViewModel, then MVC will do some magic to make For example:
public class FormViewModel {
public string input1 {get;set;}
public string input2 {get;set;}
// and so on
}
then on your Action:
public ActionResult FormPost(FormViewModel model) {
// you'll have access to model.input1, model.input2, etc
}
Lastly, for example, on your HTML Form you'll want to create:
<input type="text" name="input1" />
<input type="text" name="input2" />
or you could use Html.TextBoxFor(model => model.Input1) and the helper will name everything correctly.
The name property of your input tags, should be prefixed by the object name ("MyActionFilters")
for example:
<input type="text" name="MyActionFilters.YOUR_PROPERTY_NAME" />
btw, your action method should be annotated with HttpPost attribute.
[HttpPost]
ActionResult ActionName(MyActionFilters myFilters)

Linking Controller behaviour in MVC 3

Is it possible from a Controller to show a view, and then dependant on what that user selects in dropDownList - render another different view back in the original calling controller? Kind of a "daisy-chaining" effect.
The thinking behind this - is a user selecting a vehicle type - (associated with an ID number) in a view, back in the Controller dependant on what was chosen will render another view immediately displaying HTML according to the vehicle type they chose e.g. an HTML page for car or a boat or aeroplane etc...
If this is possbile can someone point me to a code examaple?
Actual Database Model below - but it is for documents, not vehicles!
check the method paremetares of your action method and return different views baed on that . Something like this.
public ActionResult GetInfo(string id,string vehicleTypId)
{
if(String.IsNullOrEmpty(vehicleTypeId))
{
var vehicle=GetVehicleType(vehicleTypId);
return View("ShowSpecificVehicle",vehicle) ;
}
var genericVehicle=GetVehicle(id);
return View(genericVehicle);
}
EDIT : Saying so, I seriously think you should keep those in 2 seperate Action methods. That makes your code clean and better readable. You may move the common functionality to a function and call if from bothe the action methods id needed. So i would do it in this way
Assuming you have a ViewModel for the first page( displays all vehicletypes)
public class VehicleTypesViewModel
{
//other relevant properties
public IEnumerable Types { set;get;}
public int SelectedTypeId { set;get;}
}
Your GET request for the initial view will be handled by this action result.It gets all the Vehicle types and return that to your view in the ViewModels Types property.
public ActionResult VehicleTypes()
{
VehicleTypesViewModel objVM=new VehicleTypesViewModel();
objVM.Types=dbContext.VehicleTypes.ToList();
return View(objVM);
}
and in your View called VehicleTypes.cshtml,
#model VehicleTypesViewModel
#using(Html.BeginForm())
{
#Html.DropDownListFor(Model.SelectedTypeId,new SelectList(Model.Types,"Text",Value"),"Select")
<input type="submit" value="Go" />
}
Another Action method to handle the form post. You have the selected type id here and you can get the specific details here and return a different view
[HttpPost]
public ActionResult VehicleTypes(VehicleTypesViewModel model)
{
// you have the selected Id in model.SelectedTypeId property
var specificVehicle=dbContext.Vehicles.Where(x=>x.TypeId=model.SelectedTypeId);
return View("SpecificDetails",specificVehicle);
}
Alternatively you can do a Get request for the specific vehicle using RedirecToAction method. I would prefer this approach as it sticks with the PRG pattern.
[HttpPost]
public ActionResult VehicleTypes(VehicleTypesViewModel model)
{
int typeId=model.SelectedTypeId;
return RedirectToAction("GetVehicle",new {#id=typeId});
}
public ActionResult GetVehicle(int id)
{
var specificVehicle=dbContext.Vehicles.Where(x=>x.TypeIdid);
return View(specificVehicle);
}
With Javascript : You can do a get call to the new view from your javascript also. without the HTTPpost to controller. You should add some javascript in your initial view for that
#model VehicleTypesViewModel
//Include jQuery library reference here
#Html.DropDownListFor(Model.SelectedTypeId,new SelectList(Model.Types,"Text",Value"),"Select")
<script type="text/javascript">
$(function(){
$("#SelectedTypeId").change(){
window.location.href="#Url.Action("GetVehicle","Yourcontroller")"+"/"+$(this).attr("id");
});
});
</script>
I think to get a better user experience create a partial view, and load that partial view in a div in the same page via an ajax call.
public ActionResult GetVehicalInfo(string id, string vehicleType)
{
var vehicle = GetVehicleType(id, vehicleTypId);
return PartialView("vehicle);
}

Supplying a ID to many partial views from a parent view without exposing sensitive ID's

We are using a friendly Name URL route scheme. Basically using a combination of the Principal Identity and a friendly name this can be mapped back to an identity internally (Person ID). So a route like this:
routes.MapRoute(string.Empty, "{friendlyname}/Products/{action}", new {controller = "Products", action = "List"});
Would map to a URL like this:
Adam/Products/List
This all works fine and abstracts away the internal Id of the named person which is required as well.
The problem is our Views are comprised of many partial views. When there are rendered by using the #Html.Action method they ultimately need the PersonID but from the URL we only have the 'friendly name'.
I have thought about this for a while and there are two solutions to my mind:
Pass the 'friendly name' into each of the controller action methods that return the partial views and internally the method will have to do a lookup on the currently logged in identity and the friendly name. This will give the PersonID to me and I can then efficiently query from then on. The only problem with this apporach is that due to the multiple partial views I will be querying on the currently logged in identity and friendly name for each partial view call which is innefficeint and I feel I should only have to write this code once.
Somehow query in the view and get the PersonID so it can be passed to each #Html.Action call so the partial view controller methods will not have to do that lookup themselves saving round trips to the database for the same shared informtion. The problem with this is that I am not sure of a way of doing this cleanley in the view using the DI that we use through the rest of the application.
Any thoughts on approach to would be greatly appreciated.
Thank you,
Adam
You could add the Id to the session variables and access it from within the views with:
#{var personId = int.Parse(Session["PersonId"])}
Then you can pass it directly to partial views from the Parent without it hitting the client or having to pass parameters to any controllers.
Update
You could also access the session variable from the Controller if you wanted to do the work there instead without roundtripping to the database.
EDIT
If you put the property in a model and pass it to a page that post's back then the model will not persist between posts.
If for example your controller does:
[HttpPost]
public ActionResult DoSomething(ViewModel model)
{
if(ModelState.IsValid)
{
// Logic Here
}
return View(model)
}
when the page is reloaded, the model will have forgotten about the ID.
There are a couple of ways around this. Either use #Html.HiddenFor(m => m.ID)
which will put the property in the rendered HTML, which if it is truely a sensitive piece of information, is bad.
Or you can rebuild the view model on each subsequent postback.
Hope this helps
As Marc states I could you the Session to deal with this but I have gone with using the Model as he has stated in his update. If the parent View Controller action takes in the friendly name it can do the lookup, put the PersonID into the model and then any Partial Renders can have the models value passed into them in the parent View Controllers Action's view. An example is shown below (this is demo code but it hopefully gets the point across, I would never use a static data context in real code)
Home controller
public class HomeController : Controller
{
public ActionResult Index(string friendlyName)
{
int pupilId = Data.People.Single(x => x.Name == friendlyName).PersonId;
HomeIndexViewModel homeIndexViewModel = new HomeIndexViewModel {PupilId = pupilId};
return View(homeIndexViewModel);
}
}
Home Index View
#model SharingInformationBetweenPartials.Web.Models.HomeIndexViewModel
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#Html.Action("DisplayPersonDetail", "Person", new {Model.PersonId})
The PersonController's DisplayPersonDetail method can then present the respective data it wishes using the passed in PersonId:
public class PupilController : Controller
{
[ChildActionOnly]
public ActionResult DisplayPupilDetail(int pupilId)
{
Person person = Data.People.Single(x => x.PersonId == pupilId);
return View(person);
}
}
I did try what I thought was this before but I must have got something wrong as the ViewModels properties were getting shown in the URL which is what I was trying to get away from. Anyway, I hope this helps anyone else who may be looking to do something similar. If you have any questions then let me know.
Thanks,
Adam
You can use [Bind] attribute to specify the exact properties a model binder should include in binding or use Exclude parameter on the attribute to exclude PersonId
[HttpPost]
public ActionResult EditPerson([Bind(Exclude = "PersonId")] Person person)
{
//do domething
}
You can also use [ReadOnly] attribute that model binder will understand and not assign to that property.
[ReadOnly(true)]
public int PersonId{ get; set; }
But the best approach is to use separate ViewModels: one only for viewing and one for editing.
public abstract class Person
{
public string Name { get; set; }
public string Surname { get; set; }
}
public class PersonCreateVM : Person
{
//no PersonId here
}
public class PersonEditVM : Person
{
public int PersonId{ get; set; }
}
This approach is maybe a "overkill" but when used properly and with AutoMapper http://automapper.codeplex.com/ it's an ease to work with.

Categories

Resources