MVC3 Pass additional data from ctrl to view - asp.net-mvc-3

If I were to pass additional data (other than the model) into my view, like say a list of files from a specific folder, whats the best way to do that?
I was thinking something like making a method and return a list into ViewData:
public List<string> GetFiles(int id, string cat)
{
var files = new List<string>();
var folder = "~/App_Data/uploads/" + cat + "/" + id.ToString();
foreach (var file in Directory.GetFiles(folder))
{
files.Add(file);
}
return files;
}
The controller:
ViewData["files"] = db.GetFiles(id, "random");
The view:
#foreach (var item in ViewData["files"]){ //markup }
First, ive heard that viewdata and viewbag shouldnt be used. Second, this code doesnt work. Something with Viewdata object is not enumerable. Should I make a model class for these files. If so, how? 2 models in one view? Im a bit confused how to do this proberly.

If I were to pass additional data (other than the model) into my view,
like say a list of files from a specific folder, whats the best way to
do that?
Write a view model which will contain all the properties your view needs and have your controller action pass this view model to the view. So:
public class MyViewModel
{
public List<string> Files { get; set; }
public string Foo { get; set; }
... some other properties that your view might need
}
then the controller:
public ActionResult Index(int id)
{
var model = new MyViewModel
{
Files = db.GetFiles(id, "random"),
Foo = "bar"
};
return View(model);
}
and finally in your strongly typed view:
#model MyViewModel
#foreach (var file in Model.Files)
{
<div>#file</div>
}
<div>#Html.DisplayFor(x => x.Foo)</div>

Related

MVC3 Persisting data from controller to view back to controller

I have a model containing a couple of lists:
[Display(Name = "Facilities")]
public List<facility> Facilities { get; set; }
[Display(Name = "Accreditations")]
public List<accreditation> Accreditations { get; set; }
I populate these lists initially from my controller:
public ActionResult Register()
{
var viewModel = new RegisterModel();
viewModel.Facilities = m_DBModel.facilities.ToList();
viewModel.Accreditations = m_DBModel.accreditations.ToList();
return View(viewModel);
}
When they get to my view they are populated with the DB records (great). I then pass the model to the partial view which displays these lists as checkboxes, ready for user manipulation (I have tried based on another suggestion using for loop instead of foreach loop, made no difference):
#model LanguageSchoolsUK.Models.RegisterModel
#foreach (var item in Model.Facilities)
{
#Html.Label(item.name);
#Html.CheckBox(item.name, false, new { id = item.facility_id, #class = "RightSpacing", #description = item.description })
}
When I submit the form and it ends up back at my controller this time calling the overloaded register function on the controller:
[HttpPost]
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// Do stuff
}
return View(model);
}
The problem is that the model parameter containing the lists (Facilities and Accreditations) is telling me that the lists are null.
Please can somebody tell me what I am doing wrong, why aren't they populated with the collections that I originally passed through and hopefully a way of asking whick ones have been checked?
Thanks.
I have tried based on another suggestion using for loop instead of
foreach loop, made no difference
Try again, I am sure you will have more luck this time. Oh and use strongly typed helpers:
#model LanguageSchoolsUK.Models.RegisterModel
#for (var i = 0; i < Model.Facilities.Count; i++)
{
#Html.HiddenFor(x => x.Facilities[i].name)
#Html.LabelFor(x => x.Facilities[i].IsChecked, Model.Facilities[i].name);
#Html.CheckBoxFor(
x => x.Facilities[i].IsChecked,
new {
id = item.facility_id,
#class = "RightSpacing",
description = item.description // <!-- HUH, description attribute????
}
)
}
Also you will undoubtedly notice from my answer that checkboxes work with boolean fields on your model, not integers, not decimals, not strings => BOOLEANS.
So make sure that you have a boolean field on your model which will hold the state of the checkbox. In my example this field is called IsChecked but obviously you could feel absolutely free to find it a better name.

Passing DateTime[] to the view to deploy as a dropdown in the view

I had a look at this:
ASP.NET MVC - How to pass an Array to the view?
and am struggeling with deploying my DateTime[] as a dropdownlist in the view, herewith my line but it is not correct:
<p style="color:Red"><%: Html.DropDownList("Choose a Sunday: ", (IEnumerable<SelectListItem>)ViewData["Sundays"], "--Select--")%>
</p>
In the controller i have:
[HttpGet]
public ActionResult MyAction()
{
DateTime[] allSundaysInMonth = GetDatesOfSundays(System.DateTime.Now);
ViewData["Sundays"] = allSundaysInMonth;
return View();
}
can someone help please?
thanks
Perhaps what you want to do is this:
[HttpGet]
public ActionResult MyAction()
{
IEnumerable<SelectListItem> allSundaysInMonth = GetDatesOfSundays(System.DateTime.Now).Select(day => new SelectListItem() { Text = day.ToString(), Value = day.ToString() });
ViewData["Sundays"] = allSundaysInMonth;
return View();
}
That is depending on your display format. What I'm trying to point out is that DateTime and SelectListItem may not have an implicit cast, so you get a type mismatch at runtime.
If you ever think (realize) dynamic magic variables like ViewData/ ViewBag is an evil and thinking about using strongly typed ViewModel for your view, you can do like this
create a ViewModel specific for your View (UI).
public class EventViewModel
{
public IEnumerable<SelectListItem> AllSundays { set;get;}
public string SelectedSunday { set;get;}
}
In your action method, set the value and send it to the view
public ActionResult MyAction()
{
var vm=new EventViewModel();
vm.AllSundays = GetDatesOfSundays(System.DateTime.Now).
Select(d => new SelectListItem() {
Text = d.ToString(), Value = d.ToString() }).ToList();
return View(vm);
}
Change your view like this
#model EventViewModel
#Html.DropDownListFor(x => x.SelectedSunday, Model.AllSundays, "--Select One--")
ViewBag/ViewData is bad. It makes your code ugly. Stick to strongly typed.
You cast the list to IEnumerable<SelectListItem>, but its a list of DateTimes. Casting the list to IEnumerable<DateTime> should fix it.
Or you might want to put list of SelectListItem on the ViewData instead!

How to use different type of parameter without creating new view?

I would like to create a list with a string and an int value at the same time like follows:
#Html.ActionLink("Back to List", "IndexEvent", new { location = "location" })
and
#Html.ActionLink("Back to List", "IndexEvent", new { locationID = 1 })
It didn't work. I guess MVC controller didn't get the type difference of parameter. So, I had to make a new Action as "IndexEvenyByID" but it requires to have a new view. Since I wanted to keep it simple, is there any way to use same view with respect to different parameters?
Try adding two optional parameters to the IndexEvent action like this:
public ActionResult IndexEvent(string location = "", int? locationID = null)
This should not require a new view or view model. You should have two actions as you have described, but the code could be as follows:
Controller
public ActionResult GetEvents(string location){
var model = service.GetEventsByLocation(location);
return View("Events", model);
}
public ActionResult GetEventsById(int id){
var model = service.GetEventsById(id);
return View("Events", model);
}
Service
public MyViewModel GetEventsByLocation(string location){
//do stuff to populate a view model of type MyViewModel using a string
}
public MyViewModel GetEventsById(int id){
//do stuff to populate a view model of type MyViewModel using an id
}
Basically, if your View is going to use the same view model and the only thing that is changing is how you get that data, you can completely reuse the View.
If you really want to stick to a single action and multiple type, you could use a object parameter.
public ActionResult GetEvents(object location)
{
int locationID;
if(int.TryParse(location, out locationID))
var model = service.GetEventsByID(locationID);
else
var model = service.GetEventsByLocation(location as string);
return View("Events", model);
}
Something like that (Not completly right but it gives you an idea). This, however, wouldn't really be a "clean" way to do it IMO.
(Edit)
But the 2 actions method is still by far preferable (eg. What happens if we're able to parse a location name into a int?)

How to create a DropDownList from a LINQ query in MVC3?

I need to know how I could create a drop down list to represent all the categories in my "Categories" table.
I have already extracted the names and the values of each category I need, using this LINQ query :
var dbcontext = new LNQ2SQLDataContext();
var Q = from P in dbcontext.Categories
where P.SUB_CAT == null
select P;
I can pass this "Q" to my view like this :
In Controller :
return View(Q);
And in the View :
#model IEnumerable<MyAppName.Models.Category>
But I have no idea how to use #html.DropDownListFor() to make a darn good drop down list out of the model. :|
PLUS:
I could make a SelectList from the query "Q" like this :
var category_list = new SelectList(Q, "CAT_ID", "CAT_Name");
BUT I don't know how to create a drop down list (without using ViewBag to pass the category_list to the view) from a simple SelectList, either :|
I searched through as many blogs and websites as I could. But they didn't have the solution for my problem. I only got more and more confused!
So can anybody help please ? :/
To use DropDownListFor you'll either have to have a model that has a SelectList or data to make a selectlist out of and a property to store the selected value of the dropdown OR use the ViewBag to pass the category_list. So you can go with...
Public Class MyViewModel
{
Public Integer SelectedCategory { get; set; }
Public SelectList Categories { get; set; }
}
Public Class ItemsController : Controller
{
Public ActionResult Index()
{
var dbcontext = new LNQ2SQLDataContext();
var Q = from P in dbcontext.Categories
where P.SUB_CAT == null
select P;
var vm = new MyViewModel();
vm.Categories = new SelectList(Q, "CategoryID", "Name");
return View(vm);
}
[HttpPost()]
Public ActionResult Index(MyViewModel vm)
{
var theSelectedCategory = vm.SelectedCategory;
}
}
The view would be...
#model MyViewModel
#Html.DropDownListFor(model => model.SelectedCategory, Model.Categories, "Select Category")
Note: I don't typically code in C# so I can't guarantee the syntax is exactly right.

ASP.NET MVC 3 Viewmodel Pattern

I am trying to work out the best way of using a viewmodel in the case of creating a new object.
I have a very simple view model that contains a contact object and a select list of companies.
private ICompanyService _Service;
public SelectList ContactCompanyList { get; private set; }
public Contact contact { get; private set; }
public ContactCompanyViewModel(Contact _Contact)
{
_Service = new CompanyService();
contact = _Contact;
ContactCompanyList = GetCompanyList();
}
private SelectList GetCompanyList()
{
IEnumerable<Company> _CompanyList = _Service.GetAll();
return new SelectList(_CompanyList, "id", "name");
}
I then have contact controller that uses this viewmodel and enable me to select a related company for my contact.
[Authorize]
public ActionResult Create()
{
return View(new ContactCompanyViewModel(new Contact()));
}
My issue is with the create method on the controller.
[Authorize]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Contact _Contact)
{
try
{
_Service.Save(_Contact);
return RedirectToAction("Index");
}
catch
{
return View();
}
}
The problem is that the view returns an empty contact object, but! the company id is populated, this is because the dropdown list explicitly declares its field name.
#Html.DropDownList("parent_company_id",Model.ContactCompanyList)
The standard html form fields pass the objects values back in the format of contact.forename when using the HTML.EditorFor helper...
#Html.EditorFor(model => model.contact.forename)
I can access them if I use a FormCollection as my create action method paremeter and then explicitly search for contact.value but I cannot use a Contact object as a parameter to keep my code nice and clean and not have to build a new contact object each time.
I tried passing the actual view model object back as a parameter but that simply blows up with a constructor error (Which is confusing seeing as the view is bound to the view model not the contact object).
Is there a way that I can define the name of the Html.EditFor field so that the value maps correctly back to the contact object when passed back to the create action method on my controller? Or Have I made some FUBAR mistake somewhere (that is the most likely explanation seeing as this is a learning exercise!).
Your view model seems wrong. View models should not reference any services. View models should not reference any domain models. View models should have parameterless constructors so that they could be used as POST action parameters.
So here's a more realistic view model for your scenario:
public class ContactCompanyViewModel
{
public string SelectedCompanyId { get; set; }
public IEnumerable<SelectListItem> CompanyList { get; set; }
... other properties that the view requires
}
and then you could have a GET action that will prepare and populate this view model:
public ActionResult Create()
{
var model = new ContactCompanyViewModel();
model.CompanyList = _Service.GetAll().ToList().Select(x => new SelectListItem
{
Value = x.id.ToString(),
Text = x.name
});
return View(model);
}
and a POST action:
[HttpPost]
public ActionResult Create(ContactCompanyViewModel model)
{
try
{
// TODO: to avoid this manual mapping you could use a mapper tool
// such as AutoMapper
var contact = new Contact
{
... map the contact domain model properties from the view model
};
_Service.Save(contact);
return RedirectToAction("Index");
}
catch
{
model.CompanyList = _Service.GetAll().ToList().Select(x => new SelectListItem
{
Value = x.id.ToString(),
Text = x.name
});
return View(model);
}
}
and now in your view you work with your view model:
#model ContactCompanyViewModel
#using (Html.BeginForm())
{
#Html.DropDownListFor(x => x.SelectedCompanyId, Model.CompanyList)
... other input fields for other properties
<button type="submit">Create</button>
}

Resources