Is passing the Viewmodel to the Controller a good idea? - c#

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)

Related

How to populate a Model in MVC

So I am trying to populate a model, which needs a userId. So right now, my ActionResult Index method just returns a view which prompts for the user to enter their userId. I need to grab this value and then create the viewmodel and then pass it in back to the view so that way I can do stuff like #Model.blah. I was wondering how I can do that, whether it'd be having two different action result methods, or in general how to populate models when the information you need must be queried first before constructing the viewmodel.
Here is my controller:
public ActionResult Index()
{
// Ask for UserID
return View("~/Views/FingerprintTool/Index.cshtml");
}
public ActionResult Index(int userId)
{
var response = _driver.ListFingerprints(userId);
var model = new FingerprintToolModel()
{
Fingerprints = response.Fingerprints
};
return View(model);
}
And here is my html:
model Models.Tools.FingerprintToolModel
<head>
<script type="text/javascript" src="~/Scripts/FingerprintTool.js"></script>
</head>
<body>
<h1>Fingerprint Tool</h1>
<form id="userIdForm" method="post">
Type in your UserId: <input name="userId" type="number" id="formUserId"/>
<input type="submit"/>
</form>
#if (Model != null)
{
<h1>SUCCESS</h1>
}
</body>
I also have a Javascript file that deals with the submit button be clicked and whatnot.
Here is the Js:
window.onload = function() {
$("#userIdForm")
.submit(function(e) {
e.preventDefault();
if ($("#formUserId").length == 0) {
alert("Invalid UserId");
} else {
listUserFingerprints();
}
});
}
function listUserFingerprints() {
// what to do here
}
Form:
First, update your form. You can use a simple HTML form, or the #Html.BeginForm() helper, like so:
#using Html.BeginForm()
{
#Html.AntiForgeryToken()
<label for="userId">Enter your User ID:</label>
<input type="number" name="userId" id="userId" />
<input type="submit" />
}
By default, your Html.BeginForm creates all the necessary elements and form action etc. If you prefer, you can still use standard HTML. Both do the same job.
Note the AntiForgeryToken. This is so useful when using POST form data (and even get, if you really must).
In your Controller, you can then check against this AntiForgeryToken, to protect against malicious posts or data being injected - see below.
Controller:
Create another method in your Controller, with the same name as your existing one; decorate it with the [HttpPost] attribute.
If you are using an AntiForgeryToken, you need to decorate the method with [ValidateAntiForgeryToken] also.
Like this:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(int userId)
{
// MVC's DefaultModelBinder is smart enough to map your form
// post values to objects of the correct type, given the name in the form
// Get the data from your repository etc.
var model = GetUser(userId);
// Then return this model to the view:
return View(model);
}
Notice the parameter we are looking for in the method signature matches the name attribute of the input in your form.
MVC's DefaultModelBinder is able to make the connection between the two and map the value(s) of any parameters to form values.
You can also check if your model is null (for example, that userId doesn't exist) and then return an error to the page if so.
I like to use validation errors, but you can also use ViewBag or any other kind of method.
You can do a check and add an error, like this:
// Get the data from your repository etc.
var model = GetUser(userId);
if (model == null)
{
ModelState.AddModelError("", "The user ID you entered cannot be found. Please try again");
}
// Then return this model to the view:
return View(model);
This will add a "generic" model error to the view data, which you can then process in the view. More on that, below.
View:
In order to support your view displaying your model, you need to insert an #model statement at the top of your cshtml file.
Like so:
#model MyNameSpace.Models.User
This tells the view engine what Model type to expect from the Controller. In this case I have used User, but it would be whatever your class is called.
Be sure to use the fully-qualified namespace of your class in order to access it.
Then, in your HTML code, you can access the properties of your model using #Model.YourProperty.
Like this:
...
<div>#Model.Username</div>
<div>#Model.FullName</div>
<ul>
#foreach (var fingerPrint in Model.FingerPrints){
<li>#fingerPrint.WhateverProperty</li>
}
</ul>
...
As you can see, this loops through the FingerPrints (or whatever the property is called on your model object) and prints them out in a <ul>. This is to give you an idea of how to access the data from your model.
It is a good idea to create strongly-typed views like this - as this is the WHOLE idea of MVC in the first place :)
Don't forget to add an if check around the part of the page you're access the #Model data (otherwise you will get a NullReferenceException):
#if (Model != null){
<div id="modelDataInHere">
... // Display the data from your model, nice and pretty-like
</div>
}
Note the casing difference between the declaration and printing the values to the HTML output. I won't go into detail, but this is correct and necessary. Don't mix the two up.
Over to that "Validation Error" (AddModelError) we added in the Controller.
If you're using the Html.BeginForm() helper - like in the example - then you can add a Summary in the form, somewhere. When ModelState.AddModelError() is called in your controller, this populates the view data and this error can be displayed on the page.
Like so:
#using Html.BeginForm()
{
#Html.AntiForgeryToken()
// This puts any "generic" error messages at the top of the form
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
// Original form fields
<label for="userId">Enter your User ID:</label>
<input type="number" name="userId" id="userId" />
<input type="submit" />
}
No need for any Ajax or any JavaScript in here. You're simply using MVC for what it was designed in the first place.
You can use Ajax, however.
I would encourage you to read into it. There is also a similar helper: Ajax.BeginForm(), which renders the form as an Ajax one.
A bit of JS set up is required. And your controller action could return a PartialView, rather than a full-blown one.
Have a read up on using Ajax form posts here: http://eliot-jones.com/2014/09/mvc-ajax
The finished article
Time is marching on, and this post is getting longer.
But for the sake of clarity, your view and controller should look something like below. (I've stuck in a made-up class, so you can see the where the properties come from):
View
#model YourNameSpace.Models.User
... all your other code in here...
<div>
#using Html.BeginForm()
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<label for="userId">Enter your User ID:</label>
<input type="number" name="userId" id="userId" />
<input type="submit" />
}
</div>
#if (Model != null)
{
<!-- If there is a model present, display the data for it: -->
<div>
<div>#Model.Username</div>
<div>#Model.FullName</div>
<ul>
#foreach (var fingerPrint in Model.FingerPrints)
{
<li>#fingerPrint.WhateverProperty</li>
}
</ul>
</div>
}
Controller
public ActionResult Index()
{
// This is your standard "GET" request for "Index"
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(int userId)
{
// MVC's DefaultModelBinder is smart enough to map your form
// post values to objects of the correct type, given the name in the form
// Get the data from your repository etc.
var model = GetUser(userId);
if (model == null)
{
ModelState.AddModelError("", "The user ID you entered cannot be found. Please try again");
}
// Then return this model to the view:
return View(model);
}
Sample model
public class User
{
public string Username { get; set; }
public string FullName { get; set; }
public List<FingerPrint> FingerPrints { get; set; }
}
Sincerely hope this helps you, and I wish you the best in your project.
Any questions, please feel free to ask :)
Seems like you need to work with json and AJAX method:
[HttpPost]
public ActionResult BasePage(int userId)
{
// user ID is binded to userId variable, based on the input name
var model = populate(userId);
// Remember to return the model to the view
return Json(model);
}
The Javascript for calling this would be:
function listUserFingerprints() {
$.post("/FingerprintTool/BasePage", {userId: $("#formUserId").val() }, function(model) {
console.log(model); // do whatever you want with model here
}
}

Changes to the model object not passing back to the controller

I'm loading the model to view and display it just fine. Then I make the changes to one of the fields and send it back. According to the break-point, I'm hitting the right action method but the model that's passed in contains no changes.
I suspect that I've unbound controls in the view. I've tried both the below, same problem both times.
#Html.TextBoxFor(bike => bike.Color)
<input type="text" value="#Model.Color" />
Am I not binding it correctly? How should I do this?
The controller being hit with break-point looks like this. Note that the bike that's passed in contains no changes according to the intellisense. If I make the change manually in VS, they are stored to the DB.
public ActionResult BikeStore(Bike bike)
{
...
return RedirectToAction("Bikes");
}
The model is Code First generated.
public partial class Bike
{
[Key]
public Guid Id{get; set;}
[Required]
[StringLength(999)]
public string Color { get; set; }
}
The submitting is done using this.
#Html.ActionLink("Submit", "BikeStore", "Home", #Model, null)
But if I do the following, it works, as in - the addition comes in to the controller. Not the actual contents of any of the controls on the page, though. So I'm very sure those are not bound and I can't figure out why or how to make them.
#Html.ActionLink("Submit", "BikeStore", "Home", new Bike
{
Id = Model.Id,
Color = Model.Color + "!"
}, null)
The submit action is wrong. You're passing #Model but this will be the model as it is when the view is rendered. You should be using a form with a submit button. This will bind the user edited values to the model on post.
#using (Html.BeginForm("BikeStore", "Home", FormMethod.Post)
{
// your inputs
// ...
<input type="submit" value="Submit">
}

How to post data through multiple partial view in mvc3

view:
#model
#using (Html.BeginForm("action", "Controller"))
{
#html.action("action1","controller1") //use model1
#html.action("action2","controller2") //use model2
#html.action("action3","controller3") //use model3
<button type="submit">submit</button>
}
Parent Model
{
public model model1{get; set;}
public model model2{get; set;}
public model model3{get; set;}
}
controller
[httppost]
public ActionResult Submit(parentmodel abc)
{
}
So my question is when I post the data the parentmodel is return as null but when I try as
[httppost]
public ActionResult Submit(model1 abc)
{
}
I get the form values in model1. Is my approach right? What should be done to get the form values in the parent model?
First of all always mention your model at top.
#model MyMVCModels
#Html.TextBoxFor(m => m.Model1.Name)
Here is the beauty, Model 1 value has to be appropriate while you are setting in your textboxes or controls.
Also the structuring of your Model's might not also be correct.
It's really hard to tell what you're trying to do from your question, but if I understand it correctly, you want to pass your form values to three separate partials simultaneously?
If that's the case, I'd recommend skipping the form postback and just make three ajax calls to load the partials when you click the submit button.

use id from url directly in a view

Lets say that i have an URL that looks something like this: localhost/userdetails/5 where 5 is the users ID. Is there any way to make use of the ID directly in the view (razor viewengine) and show the details? Or do i handle it in the default action in the controller?
To keep things simple now, focusing on getting the id to the view, you basically want to use the id to populate your model with data and then pass that to the view. So in your controller:
public ActionResult Index(int id)
{
UserModel model = db.Users.Where(u => u.Id == id).SingleOrDefault();
return View(model);
}
The view (very simplified) might look like this:
#model MyProject.Models.UserModel
#Html.DisplayFor(m => m.Id)
#Html.DisplayFor(m => m.Username)
This is very basic though. Eventually, you'll get to a point where you realise you should use viewmodels for your views instead of a domain model that's come directly from the data source. That in itself gives you another problem to solve in the form of mapping properties from the domain model onto your viewmodel. Typically, AutoMapper or ValueInjecter are used for that. For now though, it's probably best to just focus on passing data to a view and getting it back into a controller so that you can do something with it.
Update
This is a simple scenario which demonstrates how to get the data back into the controller. So basically, you'd have a form which you would submit:
#using (Html.BeginForm("Index", "Home"))
{
// Form elements and submit button
}
That would post the data to this action method for you to do whatever you wish with the data:
[HttpPost]
public ActionResult Index(UserModel inputModel)
{
// Check to see if the model's data was valid.
if (ModelState.IsValid)
{
// Do something in the database here.
// Then redirect to give the user some feedback.
return RedirectToAction("Thanks");
}
// The model validation failed so redisplay the view.
return View(inputModel);
}
you can use this in both the controller or in the View as an extension method.
Example: asuming your routes id holder has the default values in global.asax
public int IdFromAdress(HttpContext httpContext)
{
RouteData rd = httpContext.Request.RequestContext.RouteData;
string stringId = (string)rd.Values["id"];
return int.Parse(stringId);
{
You can get the id with this
#HttpContext.Current.Request.RequestContext.RouteData.Values["id"].ToString()
But I would reccomend to use a ViewMdoel to pass the value to the view and not the ViewBag or accessing directly from the view
You should use the model (i.e. the model passed back to your view). A ViewBag is another option but since the ID is part of the model itself, it wouldn't make any sense to do that.
View
#model User
#{
ViewBag.Title = "User Details";
}
#Model.Id;
Controller
public ActionResult UserDetails(int id)
{
return View("UserDetails", (object)id);
}
Yes you can. There is more than one way to do it, but since you've tagged your post MVC, assume you'll want to do it the 'MVC way', which means (imo) using a view model.
So you write a view model
public class MyViewModel()
{
public int ID {get; set;}
}
You populate the model in the controller and pass it to the view
public ActionResut MyView (int id)
{
var viewModel = new MyViewModel {ID = id};
return View (viewModel);
}
Then you have a strongly typed view (strongly typed to the MyViewModel, that is)
and you can reference the model's properties
#Model.ID
Then to make this useful, you can add whatever other properties you're wanting to work with to your view model. Then you can populate them in your controller before rendering the view (to show user info, for example), or let the user populate them for you in the view (using textboxes and such wrapped in a form). Then you can collect the user input in the post action in the controller like so
[HttpPost]
public ActionResult MyView(MyViewModel viewModel)
{
//do stuff with the data from the viewModel
}

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);
}

Categories

Resources