How to round-trip view-only data between views and controllers - asp.net-mvc

I'm passing data between my controller and my view using a ViewModel class. When there are validation errors, I return the ViewModel back to the view so the user can see the errors.
I'm having trouble figuring out the best way to deal with the data that is only passed from the controller to the view, which isn't passed back to the controller, such as the contents of dropdown lists.
Here's a simplified example from the project I'm working on:
I have a Widget object in my domain model that has an Employee property. I have a view that allows the user to edit this employee property by selecting their name from a drop down list.
public class WidgetFormViewModel {
// Used for a drop down list in the view
public SelectList EmployeeList { get; set; }
// This will contain the employee the user selected from the list
public int EmployeeID { get; set; }
public Widget Widget { get; set; }
}
And the controller:
// GET: /Widget/Edit/1
public ActionResult Edit(int id) {
var widget = _widgetService.GetWidgetByID(id);
var employees = _widgetService.GetAllEmployees();
var viewModel = new WidgetFormViewModel()
{
EmployeeList =
new SelectList(employees, "ID", "Name", widget.Employee),
Widget = widget,
WidgetID = widget.ID
};
return View("Edit", viewModel);
}
// POST: /Widget/Edit
public ActionResult Edit(WidgetFormViewModel viewModel) {
var existingWidget = _widgetService.GetWidgetByWidgetID(viewModel.WidgetID);
existingWidget.Employee = _widgetService.GetEmployeeByID(viewModel.EmployeeID);
// try { /* Save widget to DB */ } catch { /* Validation errors */ }
return ModelState.IsValid
// Update was successful
? (ActionResult) RedirectToAction("List")
// Model state is invalid, send the viewModel back to the view
: View("Edit", viewModel)
}
Now, here's the problem: When the ModelState is invalid and viewModel gets passed back to the view, its EmployeeList property is blank. What is the best way to deal with this?
Should I just repopulate it before returning to the view? This method seems difficult to maintain. (What if I add PageTitle and HeaderText to the view model as well? Suddenly there are more things to keep track of.) Is there another approach?

Inside of the catch block of the controller action handling the post, extract your error messages and add it to this.ModelState, then have it return this.Edit(viewModel.widgetID);.
You already have all the logic you need built up to display the view appropriately, you just want to use ModelState to make sure that errors make it back to the view.

Related

MVC Model Binding Post Values Best Practice

I'm trying to figure out if what I'm doing is flawed or acceptable. Specifically, I'm questioning the NULL value I'm getting back in the POST to Controller in 'Timeframes' property. The 'Timeframe' (singular) property DOES contain the value so all is good. However, is this just how model binding works and the property (Timeframes) that is used to populate the DDL comes back as null? Is this best practice and what I'm doing is fine? Is this a concern of sending values around that are not needed...performance concern?
Timeframe = used to return value back to Controller on Post
Timeframes = used to populate DDL values
Drop Down List Box on View:
#Html.DropDownListFor(m => m.Timeframe, Model.Timeframes)
Model:
public class ABCModel
{
public List<SelectListItem> Timeframes { get; set; }
public string Timeframe { get; set; }
}
Controller:
[HttpPost]
public void TestControllerMethod(ABCModel model)
{
//this value is null.
var timeFrames = model.Timeframes;
//this value is populated correctly
var timeFrame = model.Timeframe;
}
A form only posts back the name/value pairs of its successful controls. You have created a form control for property Timeframe, so you get the value of the selected option in the POST method.
You have not (and should not), created form controls for each property of each SelectListItem in your Timeframes property, so nothing relating to it is send in the request when the form is submitted, hence the value of Timeframes is null.
If you need to return the view because ModelState is invalid, then you need to re-populate the TimeFrames property as you did in the GET method (otherwise your DropDownListFor() will throw an exception). A typical implementation migh look like
public ActionResult Create()
{
ABCModel model = new ABCModel();
ConfigureViewModel(model);
return View(model);
}
[HttpPost]
public ActionResult Create(ABCModel model)
{
if (!modelState.IsValid)
{
ConfigureViewModel(model);
return View(model);
}
// Save and redirect
}
private void ConfigureViewModel(ABCModel model)
{
model.TimeFrames = ....; // your code to populate the SelectList
}

Asp.Net MVC 5 How to send ViewBag to Partial View

I have a _LoginPartial View and want to send data to it by ViewBag, but the Controller that I'am sending data from, doesn't have a View.
public PartialViewResult Index()
{
ViewBag.sth = // some data
return PartialView("~/Views/Shared/_LoginPartial.cshtml");
}
This code didn't work for me.
It seems you're expecting this Index action to be called when you do: #Html.Partial('_LoginPartial'). That will never happen. Partial just runs the partial view through Razor with the current view's context and spits out the generated HTML.
If you need additional information for your partial, you can specify a custom ViewDataDictionary:
#Html.Partial("_LoginPartial", new ViewDataDictionary { Foo = "Bar" });
Which you can then access inside the partial via:
ViewData["Foo"]
You can also use child actions, which is generally preferable if working with a partial view that doesn't need the context of the main view. _LoginPartial seems like a good candidate, although I'm not sure how exactly you're using it. Ironically, though, the _LoginPartial view that comes with a default MVC project with individual auth uses child actions.
Basically, the code you have would already work, you would just need to change how you reference it by using Html.Action instead of Html.Partial:
#Html.Action("Index")
Notice that you're calling the action here and now the view.
You can always pass data directly to the partial view.
public PartialViewResult Index()
{
var data = // some data
return PartialView("~/Views/Shared/_LoginPartial.cshtml", data);
}
Pass multiple pieces of data
public class MyModel
{
public int Prop1 { get; set; }
public int Prop2 { get; set; }
}
public PartialViewResult Index()
{
var data = new MyModel(){ Prop1 = 5, Prop2 = 10 };
return PartialView("~/Views/Shared/_LoginPartial.cshtml", data);
}
I passed viewBag data to my partial view like below, and I converted that viewBag data object to JSON in my partial view by using #Html.Raw(Json.Encode(ViewBag.Part));
my code sample is given below.
public async Task<ActionResult> GetJobCreationPartialView(int id)
{
try
{
var client = new ApiClient<ServiceRepairInspectionViewModel>("ServiceRepairInspection/GetById");
var resultdata = await client.Find(id);
var client2 = new ApiClient<PartViewModel>("Part/GetActive");
var partData = await client2.FindAll();
var list = partData as List<PartViewModel> ?? partData.ToList();
ViewBag.Part = list.Select(x => new SelectListItem() {Text = x.PartName, Value = x.Id.ToString()});
return PartialView("_CreateJobCardView" ,resultdata);
}
catch (Exception)
{
throw;
}
}
Here i have passed both model and viewBag .
First off, the code in your question does not run. When you do #Html.Partial("_SomeView") the Index() method you have there does not run. All #Html.Partial("_SomeView") does is render _SomeView.cshtml in your current view using the current view's ViewContext.
In order to get this to work you need a bit of functionality that's common to all the controllers in your project. You have two options: extension method for ControllerBase or a BaseController that all the controllers in your project inherit from.
Extension method:
Helper:
public static class ControllerExtensions
{
public static string GetCommonStuff(this ControllerBase ctrl)
{
// do stuff you need here
}
}
View:
#ViewContext.Controller.GetCommonStuff()
BaseController
Controller:
public class BaseController : Controller
{
public string GetCommonStuff()
{
// do stuff you need here
}
}
Other controllers:
public class SomeController : BaseController
...
...
View:
#((ViewContext.Controller as BaseController).GetCommonStuff())

mvc pass tuple data as parameter

I have a tuble like this as model.
#model Tuple<Urun,List<UrunKatagori>>
inside the view I need to pass those data to controler.
here is the my button.
Html.X().Button().Text("Guncelle").Icon(Icon.PageSave)
.DirectEvents(de =>
{
de.Click.Url = "Urunler/Guncelle";
de.Click.ExtraParams.Add(new Parameter { Name = "Urun", Value ="Model.Item1", Mode = ParameterMode.Raw });//Iguess here is wrong
})
and my controller
[HttpPost]
public ActionResult Guncelle (Urun Urun){
Urun_BLL urun_bll = new Urun_BLL();
// urun_bll.Update(mdl);
X.Msg.Notify(new NotificationConfig
{
Icon = Icon.Accept,
Title = "Working",
Html =Urun.Ad.ToString()//I need to get data here
}).Show();
return this.Direct();
}
I strongly suggest that you create a viewmodel class, rather then passing a Tuple e.g.
public class GuncelleViewModel
{
public Urun Urun { get ;set; }
public List<UrunKatagori>> UrunKatagori { get; set; }
}
Then you can pass that to the view like so:
[HttpPost]
public ActionResult Guncelle (Urun Urun)
{
Urun_BLL urun_bll = new Urun_BLL();
// urun_bll.Update(mdl);
X.Msg.Notify(new NotificationConfig
{
Icon = Icon.Accept,
Title = "Working",
Html =Urun.Ad.ToString()//I need to get data here
}).Show();
var viewModel = new GuncelleViewModel()
viewModel.Urun = Urun;
viewModel.UrunKatagori = // TODO - However you get the categories.
return View(viewModel);
// this.Direct(); What does this.Direct() do? Replace it with calling the view instead, much cleaner.
}
In the view, use the following model
#model GuncelleViewModel
Useing a viewmodel class, which is associated one-to-one with a view file (*.cshtml), is a very good practise. It can help keep your design clean and more flexible, rather then passing specific data types, such as Tuple.

mvc passing data between controller and view

I am developing an application. I have created a view and a controller. The view has a button, on the click of which I am supposed to do database operations. I have put the database operations in the model, I am creating the object of model in the controller. On clicking the button the action is handled by a method in the controller, and the object of the model is created to get the records from the database. I would like to know if there is any way to display this data in the view.Is the approach correct or the view is supposed to interact with model directly to get the data.
Following is the code in controller that gets invoked on the button click
public ActionResult getRecord()
{
DataModel f_DM = new DataModel();
DataTable f_DT = f_DM.getRecord();
return View();
}
DataModel is the model class with simply a method "getRecord".
Any help will be highly appreciated.
I would like to add that i am using vs2010 and mvc4
Regards
you should write the logic of retrieving data in your controller. Store all your data in view model and pass it to the view.
for eg.
Model
namespace Mvc4App.Models
{
public class Product
{
public string Name { get; set; }
}
public class ProductViewModel
{
public Product Product { get; set; }
public string SalesPerson { get; set; }
}
}
Controller
public class ProductController : Controller
{
public ActionResult Info()
{
ProductViewModel ProductViewModel = new ProductViewModel
{
Product = new Product { Name = "Toy" },
SalesPerson = "Homer Simpson"
};
return View(ProductViewModel);
}
}
View
#model Mvc4App.Models.ProductViewModel
#{ ViewBag.Title = "Info"; }
<h2>Product: #Model.Product.Name</h2>
<p>Sold by: #Model.SalesPerson</p>
This is the best known practice to pass data from controller to the view.
you may use other techniques also like,
1. ViewData
2. ViewBag
3. TempData
4. View Model Object
5. Strongly-typed View Model Object
Yes, it's possible, but actually now very logical way to to this.
Lets follow your way. You have some View were you have a button, that will trigger this action.
For ex:
public ActionResult Index()
{
return View();
}
Inside view you can have a Ajax link, that will trigget your getRecord method:
<div id="GetDataDiv"></div>
<div>
#Ajax.ActionLink("Get Record", "getRecord", "ControllerName", null, new AjaxOptions() { HttpMethod = "GET", UpdateTargetId = "GetDataDiv" })
</div>
In the getRecord method you should have:
public ActionResult getRecord()
{
DataModel f_DM = new DataModel();
DataTable f_DT = f_DM.getRecord();
return PartialView(f_DT);
}
And in View it should be:
#model DataTable
#Model.PropertyOne #Model.PropertyTwo
It should works for you.
Actually same exaple here: http://www.dotnetpools.com/Article/ArticleDetiail/?articleId=151

Two data sources in one create view

This is what my data model classes look like:
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Position Position { get; set; }
}
public class Position
{
public string Title { get; set; }
}
I have a Create view where I want to have two text boxes for first name and last name, and then a dropdown box that has the position title. I tried doing it this way:
View (only the relevant part):
<p>
<label for="Position">Position:</label>
<%= Html.DropDownList("Positions") %>
</p>
Controller:
//
// GET: /Employees/Create
public ActionResult Create()
{
ViewData["Positions"] = new SelectList(from p in _positions.GetAllPositions() select p.Title);
return View();
}
//
// POST: /Employees/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Employee employeeToAdd)
{
try
{
employeeToAdd.Position = new Position {Title = (string)ViewData["Positions"]};
_employees.AddEmployee(employeeToAdd);
return RedirectToAction("Index");
}
catch
{
return View();
}
}
However, when I click submit, I get this exception:
System.InvalidOperationException was unhandled by user code
Message="There is no ViewData item of type 'IEnumerable<SelectListItem>' that has the key 'Positions'."
I'm pretty sure I'm doing this wrong. What is the correct way of populating the dropdown box?
You can store:
(string)ViewData["Positions"]};
in a hiddn tag on the page then call it like this
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Employee employeeToAdd, string Positions)
{
In the Create() (WITH POST ATTRIBUTE) employee since the ViewData["Positions"] is not set you are getting this error. This value should form part of your post request and on rebinding after post should fetch it from store or get it from session/cache if you need to rebind this..
Remember ViewData is only available for the current request, so for post request ViewData["Positions"] is not yet created and hence this exception.
You can do one quick test... override the OnActionExecuting method of the controller and put the logic to fetch positions there so that its always availlable. This should be done for data that is required for each action... This is only for test purpose in this case...
// add the required namespace for the model...
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
// add your logic to populate positions here...
ViewData["Positions"] = new SelectList(from p in _positions.GetAllPositions() select p.Title);
}
There may be other clean solutions to this as well probably using a custom model binder...
I believe that ViewData is for passing information to your View, but it doesn't work in reverse. That is, ViewData won't be set from Request.Form. I think you might want to change your code as follows:
// change following
employeeToAdd.Position = new Position {Title = (string)ViewData["Positions"]};
// to this?
employeeToAdd.Position = new Position {Title = (string)Request.Form["Positions"]};
Good luck!

Resources