I have a DropDropListFor that looks like this:
using (Ajax.BeginForm("FilterListingsWorkflow", "Listing",
new {
categoryguid = Model.SelectedCategoryGuid,
workflowstatus = Model.SelectedWorkflowStatus,
entityName = Model.EntityName,
},
new AjaxOptions {
HttpMethod = "POST",
UpdateTargetId = "listingTable",
InsertionMode = InsertionMode.Replace
}))
{
<p>Filter listings by Workflow Status:</p>
#Html.DropDownListFor(m =>
Model.SelectedWorkflowStatus,
Enum.GetValues(typeof (WorkflowStatus))
.Cast<WorkflowStatus>()
.Select(v =>
new SelectListItem {
Text = v.ToString(),
Value = ((int) v).ToString()
}),
new { onchange = "$(this.form).submit()" })
}
As you can see, there are three objects that are being sent to the controller:
Model.SelectedCategoryGuid, Model.SelectedWorkFlowStatus and EntityName.
WorkFlowStatus is an enum, and for some reason, whenever I click on any of the enums in the DropDownList, it always comes out to be the FIRST one.
I have three Enum values which get inserted in to the DropDownList which are:
Draft, Published and Archived.
So, whenever I click on any one of them and when I hover over the variable in the Controller, I see that the parameter variable: workflowstatus is always Draft (which is the first one).
Does anyone know why the CORRECT enum isn't being passed over?
It's really bugging me...
The name of your DropDownList is SelectedWorkflowStatus. So make sure your controller action takes such parameter as argument:
[HttpPost]
public ActionResult FilterListingsWorkflow(WorkflowStatus selectedWorkflowStatus)
{
...
}
That's where you will get the correct value. I can see that you are setting some route values for your form:
new {
categoryguid = Model.SelectedCategoryGuid,
workflowstatus = Model.SelectedWorkflowStatus,
entityName = Model.EntityName,
}
But don't look the workflowStatus parameter in your action. This guy will contain the old value, the one that was hardcoded in the form when the view first rendered. The fresh value is stored in the selectedWorkflowStatus parameter coming from your DropDown.
All this being said the proper way to handle this situation is (as always in ASP.NET MVC) to use a view model:
public class MyViewModel
{
public Guid SelectedCategoryGuid { get; set; }
public WorkflowStatus WorkflowStatus { get; set; }
public string EntityName { get; set; }
public WorkflowStatus SelectedWorkflowStatus { get; set; }
}
that your controller action will take:
[HttpPost]
public ActionResult FilterListingsWorkflow(MyViewModel model)
{
// model.SelectedWorkflowStatus will contain the value from the DropDown
// model.WorkflowStatus will contain the initial value
...
}
Also please replace:
m => Model.SelectedWorkflowStatus
with:
m => m.SelectedWorkflowStatus
in your DropDown. You don't need to capture the model in a closure.
Related
I am trying to pass a parameter value from my url back to the view model. Here is the url: StudentDetails/MarkingToolView/1?studentId=1
I am trying to get the first parameter of 1 (after MarkingToolview/) so I can post it back to my database
my viewModel:
public class MarkingVM
{
public int? ModuleID { get; set; }
}
my view:
#Html.HiddenFor(m => m.ModuleID, new { id = ??? })
What is the best way to do this?
I think you are asking how to get the url parameter into your view perhaps? Something like the following will populate your view model.
Controller
public action MarkingToolView(int studentId)
{
MarkingVM vm = new MarkingVM(); //or however that is initialized.
vm.ModuleID = studentId;
return View(vm);
}
I'm building an app with ASP.NET MVC 4. I'm binding my model to a view. In my view, I need a drop down list. That drop down list needs to show quarters. The quarters should be displayed as "Q1", "Q2", "Q3", and "Q4". My model, only has quarter numbers. They are defined like this:
public List<short> Quarters = new List<short>() { get; set; }
public short? SelectedQuarter = null;
public void Initialize() {
Quarters.Add(1);
Quarters.Add(2);
Quarters.Add(3);
Quarters.Add(4);
}
Somehow, I need to prepend "Q" to each value. However, I'm not sure how to do this in ASP.NET MVC. How does someone do this?
Thanks!
Create a SelectList to be used by DropdownListFor() so that you bind the selected option to property SelectedQuarter, but display the 'friendly' name.
View model
public class MyViewModel
{
[Display(Name = "Quarter")]
[Required]
public short? SelectedQuarter { get; set; } // must be a property, not a field!
IEnumerable<SelectListItem> QuarterList { get; set; }
}
Controller
public ActionResult Edit()
{
MyViewModel model = new MyViewModel();
ConfigureViewModel(model);
return View(model);
}
public ActionResult Edit(MyViewModel model)
{
if(!ModelState.IsValid)
{
ConfigureViewModel(model);
return View(model);
}
// model.SelectedQuarter contains the selected value
}
private void ConfigureViewModel(model)
{
model.SelectedQuarter = new List<SelectListItem>()
{
new SelectListItem() { Value = "1", Text = "Q1" },
new SelectListItem() { Value = "2", Text = "Q2" },
new SelectListItem() { Value = "3", Text = "Q3" },
new SelectListItem() { Value = "4", Text = "Q4" },
}
}
View
#model MyViewModel
#using(Html.BeginForm())
{
#Html.LabelFor(m => m.SelectedQuarter)
#Html.DropDownListFor(m => m.SelectedQuarter, Model.QuarterList, "-Please select-")
#Html.ValidationMessageFor(m => m.SelectedQuarter)
<input type="submit" />
}
Assuming you have this property:
public List<short> Quarters { get; set; }
Then in your view or any other consuming code you can generate a list of strings with something like:
model.Quarters.Select(q => "Q" + q)
or:
model.Quarters.Select(q => string.Format("Q{0}", q))
However, semantically it really feels like this belongs on a view model and not in consuming code. Ideally the view should only ever need to bind directly to properties on the view model, not transform those properties. Something like this:
public IEnumerable<string> QuartersDisplay
{
get { return Quarters.Select(q => string.Format("Q{0}", q)); }
}
Then consuming code can just bind to that property:
model.QuartersDisplay
(If the model is a domain model then I'd recommend introducing a view model between the domain and the view, since this wouldn't belong on a domain model.)
Thinking about this a little more... Do you want one property with both the displays and the backing values for the drop down list? That would likely be a IDictionary<short, string>, I imagine? Something like this:
public IDictionary<short, string> QuartersOptions
{
get { return Quarters.ToDictionary(q => q, q => string.Format("Q{0}", q)); }
}
In which case you'd bind to that property:
model.QuartersOptions
Keep in mind that a drop down list often binds to two things. The property which holds the list of possible values (which is what we've built here) and the property which holds the selected value (which remains your SelectedQuarter property).
I have a search page with four text boxes (ID, batchID, EmployeeNumber, RefNumber)all are numbers. I don't want to use query string to send any of these values to controller. So I am using Form.Post method like below:
#using (Html.BeginForm("Details", "Search", FormMethod.Post, new { #id = Model.id }))
But I want to make it global, so that based on which text box user uses to search, that value should be send to the controller and it's type also if possible(Like they entered ID or batchID or....) so that it will be easy for me to search the database accordingly. Please somebody help.
FYI: my route looks like this in global.asax
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
I am actually thinking to send value from a javascript method where i do all the conditions check.
You could define a view model:
public class SearchViewModel
{
public int? Id { get; set; }
public int? BatchID { get; set; }
public int? EmployeeNumber { get; set; }
public int? RefNumber { get; set; }
}
and then have the controller action you are posting to take this view model as parameter and you will be able to retrieve which values did the user entered in the textboxes:
[HttpPost]
public ActionResult Details(SearchViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
if (model.Id != null)
{
// the user entered something into the textbox containing the id
}
if (model.BatchId != null)
{
// the user entered something into the textbox containing the batch id
}
...
}
I've read many articles which they state that querying should not be placed in the Controller, but I can't seem to see where else I would place it.
My Current Code:
public class AddUserViewModel
{
public UserRoleType UserRoleType { get; set; }
public IEnumerable<SelectListItem> UserRoleTypes { get; set; }
}
public ActionResult AddUser()
{
AddUserViewModel model = new AddUserViewModel()
{
UserRoleTypes = db.UserRoleTypes.Select(userRoleType => new SelectListItem
{
Value = SqlFunctions.StringConvert((double)userRoleType.UserRoleTypeID).Trim(),
Text = userRoleType.UserRoleTypeName
})
};
return View(model);
}
The View:
<li>#Html.Label("User Role")#Html.DropDownListFor(x => Model.UserRoleType.UserRoleTypeID, Model.UserRoleTypes)</li>
How do I retain the View Model and Query and exclude the User Type that should not show up?
I think that you are doing it just fine.
Any way... all you can do to remove the querying logic from controller is having a ServiceLayer where you do the query and return the result.
The MVC pattern here is used correctly... what your are lacking is the other 2 layers (BusinessLayer and DataAccessLayer)... since ASP.NET MVC is the UI Layer.
UPDATE, due to comment:
Using var userroletypes = db.UserRoleTypes.Where(u=> u.UserRoleType != 1);
is OK, it will return a list of UserRoleType that satisfy the query.
Then, just create a new SelectList object using the userroletypes collection... and asign it to the corresponding viewmodel property. Then pass that ViewModel to the View.
BTW, I never used the db.XXXX.Select() method before, not really sure what it does... I always use Where clause.
SECOND UPDATE:
A DropDownList is loaded from a SelectList that is a collection of SelectItems.
So you need to convert the collection resulting of your query to a SelectList object.
var userroletypes = new SelectList(db.UserRoleTypes.Where(u=> u.UserRoleType != 1), "idRoleType", "Name");
then you create your ViewModel
var addUserVM = new AddUserViewModel();
addUserVM.UserRoleTypes = userroletypes;
and pass addUserVM to your view:
return View(addUserVM );
Note: I'm assuming your ViewModel has a property of type SelectList... but yours is public IEnumerable<SelectListItem> UserRoleTypes { get; set; } so you could change it or adapt my answer.
I don't see anything wrong with your code other than this db instance that I suppose is some concrete EF context that you have hardcoded in the controller making it impossible to unit test in isolation. Your controller action does exactly what a common GET controller action does:
query the DAL to fetch a domain model
map the domain model to a view model
pass the view model to the view
A further improvement would be to get rid of the UserRoleType domain model type from your view model making it a real view model:
public class AddUserViewModel
{
[DisplayName("User Role")]
public string UserRoleTypeId { get; set; }
public IEnumerable<SelectListItem> UserRoleTypes { get; set; }
}
and then:
public ActionResult AddUser()
{
var model = new AddUserViewModel()
{
UserRoleTypes = db.UserRoleTypes.Select(userRoleType => new SelectListItem
{
Value = SqlFunctions.StringConvert((double)userRoleType.UserRoleTypeID).Trim(),
Text = userRoleType.UserRoleTypeName
})
};
return View(model);
}
and in the view:
#model AddUserViewModel
<li>
#Html.LabelFor(x => x.UserRoleTypeId)
#Html.DropDownListFor(x => x.UserRoleTypeId, Model.UserRoleTypes)
</li>
I am trying to use the Html.ListBoxFor helper to show a list box and return the selected Id. Is there a problem with the dataValueField not being a string?
If the SelectList contained in the model uses integers as the dataValueField then I get a "Value cannot be null - Parameter name: Source" exception raised when the list is rendered in the view.
If the Id is changed to a string then everything works and the selected Id is passed back to the view.
Any ideas?
Here is the controller (based on a cut down new project)
namespace Mvc2.Controllers
{
public class ViewModel
{
public int TestId { get; set; } // if this is a string it works ok
public SelectList ListData {get; set;}
}
[HandleError]
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new ViewModel();
model.TestId = 1; // code corrected after Lazarus' comment
var lst = new[] { new { Id = 1, Name = "cat" }, new { Id = 2, Name = "dog" } };
model.ListData = new SelectList(lst, "Id", "Name");
return View("TestView", model);
}
public ActionResult TestSubmit(ViewModel returnedModel)
{
int i = 99; // break here - returnedModel has correct TestId when declared as string
}
}
}
here is the View - crashes on the ListBoxFor line
<%using (Html.BeginForm("TestSubmit", "Home")) { %>
<%=Model.TestId %><br />
<%=Html.ListBoxFor(m => m.TestId, Model.ListData) %>
<br />
<input type="submit" value="Save" />
<%} %>
The expression you are passing for the selected values needs to be IEnumerable because ListBoxFor supports multiple selected items.
Answering my own question;
I am unconviced by the comments that this might be a bug which is waiting to be fixed because I get it in RC2 and in MVC 1 (I copied the code back to a project in that release).
Anyway I have implemented a work around for now which is to:-
(a) Add a dummy string version of the Id to the model (TestId)
public class ViewModel
{
public string TestId { get; set; } // dummy Id as a string
public List<DataToShow> Data { get; set; }
public SelectList ListData {get; set;}
}
(b) Display the list but retrieve the value as the dummy TestId - note that the list still dumps the data values as integers!
<%=Html.ListBoxFor(m => m.TestId, Model.ListData) %>
(c) Copy the dummy string value into its proper integer location in the action
public ActionResult TestSubmit(ViewModel returnedModel)
{
MyModel.DataId = Int32.Parse(returnedModel.TestId);
Hope this is of some Help.
This is a known issue with ASP.NET MVC 2. It should be fixed in the March release.