Models to and from View - c#

I have a model that has a PersonId int, and also, a List<SelectListItems> People for people. The UI allows me to select a person from the drop down model, and save the value into PersonId.
That works, but if there is an error in my ModelState, caused by another field, I do this:
if (ModelState.IsValid == false)
{
return View(model);
}
Problem is, the object holding the list of people is NULL in the returned model. Do I really need to repopulate it from the database again, or can this somehow be 'stored', and only populated when I initially create the view?

Yes. You need to load the SelectView again.
Only values in your inputs are posted (hidden or normal ones)... so anything to be posted must be in those inputs.
If you want to avoid going to the database... you should cache that list.
Here you can see an example for the caching logic: https://stackoverflow.com/a/349111/7720

Yes, you need to repopulate it. Only the selected value of a dropdown list is sent via a form. It's perfectly normal to do this. In general, if you have data available on the server, it always makes sense to cache it/query for it again, rather than trusting any input from a user.
To conveniently build SelectLists, rather than using SelectListItem, I use a method on a base controller:
[NonAction]
public SelectList BuildSelectList<TSource>(IEnumerable<TSource> source,
Expression<Func<TSource, int>> valueKey, Expression<Func<TSource, string>> textKey,
object selectedValue = null)
{
var selectedValueKey = ((MemberExpression)(MemberExpression)valueKey.Body).Member.Name;
var selectedTextKey = ((MemberExpression)(MemberExpression)textKey.Body).Member.Name;
return new SelectList(source, selectedValueKey, selectedTextKey, selectedValue);
}
Note the use of NonActionAttribute, which is used to indicate that a public controller method is not an action method.
Then in the controller, it's easy to build any list again, without polluting the actions too much. For example:
[HttpPost]
public ActionResult Index(SomeViewModel model)
{
if (ModelState.IsValid)
return RedirectToAction("Success");
// The model wasn't valid, so repopulate the dropdown
model.People = BuildSelectList(db.People, m => m.Id,
m => m.Name, model.PersonId);
return View(model);
}
You could do something similar for SelectListItem, rather than manually rebuilding your lists each time.

Related

Lambda expression to fetch two column in a table but how can i access this in my view using viewbag

I am using a lambda expression to fetch two column in a table but how can i access this in my view using viewbag, and what will be the type if we fetch two columns from a table using lambda expression.
Ex:
GatesEntities Gates = new GatesEntities();
ViewBag.Index = Gates.Concepts.OrderBy(s => s.concept_id).Select(s => new { s.Concept_Name,s.Session_Id });
return View();
I have 5 sessions, in each session i have some concepts. Now I need to fetch concept names for each session in my view using viewbag.
Please help me with this, I wasted lot of time on this..
GatesEntities Gates = new GatesEntities();
ViewBag.Index = Gates.Concepts.OrderBy(s => s.concept_id).ToList();
return View();
View code
#foreach(var c in (List<Concepts>)ViewBag.Index)
{
#c.Concept_Name
}
The syntax of new { ... } means you're creating what's referred to as an "anonymous object". These are typed as object and require that you reference the properties inside "dynamically", which is to say at runtime.
As a result, it's a really bad idea to use this for things like views, where you're only getting runtime compilation to begin with. At least in something like a controller action, you'll get a bit of compile-time error checking, but in views this is just a time-bomb waiting to explode.
Classes are cheap, and if you're going to do something like this is far preferable to create a class to hold the values as then you'll be strongly-type the whole way through. That means less errors as Visual Studio will properly scream at you as you're writing the code and compile-time errors if you make a mistake anyways. For example (guessing at your types here):
public class ConceptViewModel
{
public string Concept_Name { get; set; }
public Guid Session_Id { get; set; }
}
Then:
var model = Gates.Concepts.OrderBy(s => s.concept_id).Select(s => new ConceptViewModel {
Concept_Name = s.Concept_Name,
Session_Id = s.Session_Id
});
return View(model);
And finally in your view:
#model IEnumerable<ConceptViewModel>
// whatever
Viewbag.Index is an IEnumerable(because Gates.Concepts.OrderBy(s => s.concept_id).Select(s => new { s.Concept_Name,s.Session_Id }) returns a collection), so iterate over it on the view.
#foreach(Concept i in (IEnumerable<Concept>)Viewbag.Index){
<p>#i.Concept_Name</p>
<p>#i.Session_Id</p>
}

TextBoxFor displaying initial value, not the value updated from code [duplicate]

This question already has answers here:
Reset the value of textarea after form submission
(9 answers)
Closed 8 years ago.
I have an MVC application that displays a value. This is the controller:
public ActionResult Index(DataSites DataSiteList)
{
if (DataSiteList.Latitude != null)
{
DataSites test = new DataSites();
test.Latitude = "LATITUDE";
return View(test);
}
return View(DataSiteList);
}
public ActionResult SomeInformation()
{
DataSites test1 = new DataSites();
test1.Latitude = "LATITUDE2";
return RedirectToAction("Index", test1);
}
The View:
#model miniproj2.Models.DataSites
<p>
#Html.TextBoxFor(x => x.Latitude)
</p>
And the Model:
public class DataSites
{
public string Latitude { get; set; }
}
When I go to /Home/SomeInformation, the DataSites' Latitude property is set to "LATITUDE2". Then redirects to the Index() action in the controler, sets the property to "LATITUDE" and returns the view.
When it shows the view, it displays the value "LATITUDE2" as set in the redirect. Shouldn't "LATITUDE" be displayed?
Your problem is (step by step)
Your SomeInformation() method sets the value of test1.Latitude
to "LATITUDE2".
You then pass that model to your Index() method using the overload
of RedirectToAction that accepts an object. Internally this uses
reflection to build a RouteValueDictionary based on the properties
of your model (in this case its simply latitude="LATITUDE2").
When you hit the Index method the model is bound by the DefaultModelBinder and now the value of DataSiteList.Latitude is "LATITUDE2" (which is why you enter
the if block)
In the process of binding, the DefaultModelBinder sets the
ModelStatevalue of Latitude to "LATITUDE2". Any attempts to set
the value of Latitude are now ignored because the view uses
ModelState value to render the control.
It not clear what your trying to do here. You can make it work as you expect by adding ModelState.Clear(); as the first line of your Index() method. This clears all existing ModelState values an you can now set the value to "LATITUDE".
But your if block makes no sense. Perhaps you were just doing some kind of test, but you may as well remove the parameter from the Index() method and just initialize a new instance of DataSites in the method.
Edit
To give a bit more information as to why updating a model property has no affect once ModelState has been set.
Imagine you have a form to collect user information where the model contains int Age. The user is asked to enter their age and someone enters "I'm five next week!". Of course this wont bind to an int so the DefaultModelBinder adds the value (the attemptedValue) and adds a ModelStateError.
When the view is returned it will typically display an error message such as "The field Age must be a number". If the html helper rendering the control used the model value, then it would display "0" (the default value for int). It would be somewhat confusing for the user to see "0" in the textbox and next it a message saying it must be a number (What! but zero is a number and what the heck happened to what I entered?). So instead, the helper uses the value from ModelState and now the users sees "I'm five next week!" and an associated error message that makes sense for the value.
So even though you thoughts were that "its not logical", there is actually some logic to this behavior.
You are not setting Altitude. It will be null so the code will never go into this block and set Lattitude to "LATTITUDE"
if (DataSiteList.Altitude != null)
{
DataSites test = new DataSites();
test.Latitude = "LATITUDE";
return View(test);
}

return RedirectToAction() not passing object

I have two partialViews in my ASP.NET MVC4 Application-
[HttpGet]
public ActionResult Autocomplete_Search(string accountHead, List<LedgerModel> ledge)
{
if (!String.IsNullOrEmpty(accountHead)) {
ledge = (from u in db.LedgerTables
where u.AccountHead.Contains(accountHead) && u.FKRegisteredRecord == this.LoggedInUser.RegisterID
select new LedgerModel {
AccID = u.AccID,
Place = u.Place,
AccountHead = u.AccountHead,
DateAccountHead = Convert.ToDateTime(u.DateAccountHead) != null ? Convert.ToDateTime(u.DateAccountHead) : DateTime.Now
}).ToList();
return RedirectToAction("_ProductSearchList", ledge);
}
return View();
//return Json(ledge, JsonRequestBehavior.AllowGet);
}
And-
public ActionResult _ProductSearchList(List<LedgerModel> ledge) {
List<LedgerModel> ledger = null;
if (ledge != null) {
ledger = (from u in ledge
select new LedgerModel {
AccID = u.AccID,
Place = u.Place,
AccountHead = u.AccountHead,
DateAccountHead = Convert.ToDateTime(u.DateAccountHead) != null ? Convert.ToDateTime(u.DateAccountHead) : DateTime.Now
}).ToList();
return PartialView(ledge);
}
else {
return PartialView(ledge);
}
}
Okay now when I send string through a textbox, Action AutoComplete_Search is called. At the time of redirection to another method named _ProductSearchList I am sending an object ledge of listType to This method. But It says ledge null in _ProductSearchList action's parameters.
However this object is a list type and contains records. How do I get this object ledge which is redirected to action _ProductSearchList?
The second parameter taken by RedirectToAction is not a model, it is a route.
That's why you are not receiving what you expect in your _ProductSearchList action.
I'm not quite sure that something like this would work because i don't know how a list of complex objects could be serialized in the url (or even if this is recommanded), but here is what would be expected :
return RedirectToAction("_ProductSearchList", new { ledge = ledge });
To pass your list, you have the TempData option (quote from MSDN) :
An action method can store data in the controller's TempDataDictionary
object before it calls the controller's RedirectToAction method to
invoke the next action. The TempData property value is stored in
session state. Any action method that is called after the
TempDataDictionary value is set can get values from the object and
then process or display them. The value of TempData persists until it
is read or until the session times out. Persisting TempData in this
way enables scenarios such as redirection, because the values in
TempData are available beyond a single request.
Don't forget to take a look at Using Tempdata in ASP.NET MVC - Best practice before using it.
In the first you can`t get List ledge in get request in Autocomplete_Search.
You can`t pass complex object in redirecting. You can only pass a simple scalar value.
Check answer in this thread:
send data between actions with redirectAction and prg pattern
Thanks all for giving time on this issue.
As #Damian S described about complex object redirecting is notable advice for me.
However I was able to find simplest solution to this problem to use DataDictionary in C#.
I managed it with using TempData[] to store details in easiest way I guess because it is most Precise and trivial technique.
Using TempData[]
Storing record in TempData[] in AutoComplete_Search() controller-
TempData["Records"]= ledge;
Usage in ProductSearchList controller
List<ledgerModel> ledge= (List<ledgerModel>)TempData["Records"];
Solved my problem and headache of playing with objects methods to methods.!

How do I pass a property expression back to the controller?

I have a view that displays a table of data that I want to be able to sort by specifying a property on the row type.
My model (so far) is:
class Model
{
List<DataType> data;
Expression<Func<DataType, object>> SortProperty;
}
I've tried creating that in the view to be passed into my controller as follows:
<a href='<%= Url.Action("Index", "Approvals", new IndexModel() { Page = Model.Page, SortProperty = ((ApprovalModel m) => m.Id)}) %>'>Id</a>
which renders as:
<a href='/PartsLegislation/Approvals.aspx/Approvals?SortProperty=m%20%3D%3E%20Convert(m.Id)'>Id</a>
so it sort of looks like it'll work (all-be-it with a Convert expression in there), however in the controller SortProperty is always null.
TL;DR: How can I pass an expression pointing to a property from my view to the controller?
Edit: My controller action is as below:
[HttpGet]
public ActionResult Index(Model viewModel)
{
....
viewModel.Approvals = PartsDC.Repository<Approval>()
.Where(a => !a.Deleted)
.OrderBy(viewModel.SortExpression)
.Skip((viewModel.Page ?? 0) * RowsPerPage)
.Take(RowsPerPage)
.Select(a => Mapper.Map<Approval, ApprovalHeaderModel>(a))
.ToList();
...
}
As far as I know you can't pass complex objects via URL. You can pass for example string with name of property.
However if you want to just sort table maybe you should think about another approach to this problem. Check this http://www.kryogenix.org/code/browser/sorttable/ .
It allows you to simply sort your table by clicking on headers.

Returning Multiple Lists in ASP.NET MVC (C#)

I'm somewhat new to the ASP.NET MVC architecture and I'm trying to sort out how I could return multiple sets of data to the view.
public ActionResult Index(string SortBy)
{
var actions = from a in dbActions.Actions
orderby a.action_name
ascending
select a;
return View(actions.ToList());
}
This code works very well for returning a single dataset. The situation I have is a that I've got a list of objects, each of which has a subset of objects that I want to display in a hierarchy on the output page. I'm not sure whether I'm looking for the quick way or the right way, thoughts on both avenues would be very much appreciated.
Thanks!
You could pass them through ViewData, an object that is passed from the controller to the view. The controller would look like this:
ViewData["ActionList"] = actions.ToList();
Retrieving it in the view:
<% foreach (var action in (List)ViewData["ActionList"]) %>
ViewData as described above is the quick way. But I beleieve it makes more sense to wrap the lists in a single object model which you then pass to the View. You will also get intellisense...
That subset of objects could/should be returned by a property on the Action (assuming db.Actions returns Action objects).
public class Action
{
//...
public IEnumerable<SubAction> SubActions
{
get { return do-what-ever; }
}
//...
}
Nothing special MVC'ish here...
In your view you just loop through both:
<%
foreach (Action a in ViewData.Model as IList<Action>)
{
foreach (SubAction sa in a.SubActions)
{
// do whatever
}
}
%>

Categories