Change Name of Html.DropDownListFor - MVC Razor - asp.net-mvc

I am creating a form with a drop down for a property named "Event". Unfortunately this is a reserved word so it won't work as usual:
#Html.DropDownListFor(x => x.Event, new SelectList(ViewData["Events"] as IEnumerable<JFS.Data.Model.Event>, "Id", "Name"))
In the controller I can't take this value separately as it is a reserved word, so if I do this:
public ActionResult Create(int event)
{
etc etc
}
It throws an error.
What I would ideally like to do is change the name of the dropdown list, something like this:
#Html.DropDownListFor(x => x.Event as eventId, new SelectList(ViewData["Events"] as IEnumerable<JFS.Data.Model.Event>, "Id", "Name"))
But that doesn't work. Anyone know of the correct way to change the name? :-)

You cannot change the name generated by HTML helpers and this is by design. What you could do instead is to change the name of your action parameter and prefix it with # which allows to use reserved words in C# as variable names:
public ActionResult Create(int #event)
{
etc etc
}
But in general it is not recommended to use reserved words as variable names unless absolutely necessary. And in your case it is not absolutely necessary because there's a much better solution which of course consists in using a view model:
public class CreateEventViewModel
{
public int Event { get; set; }
}
and then having your controller action take this view model as argument:
public ActionResult Create(CreateEventViewModel model)
{
etc etc
}

Related

Submit a List from View to Controller

can anyone please show me the code for posting a list on button click from view to controller in mvc3-asp. i have a list in which one of the list element is check box.at first i have to select some of the values from list then submit it to save the selected values only.
Your question is two broad to answer. So i will give you a generic example which you need to customize according to your scenario.
Assuming your view is to assign some tasks to users. Each user can be assigned to one ore more tasks.
So i will create a viewmodel like this.
public class UserViewModel
{
public int UserId { set;get;}
public string Name { set;get;}
public List<Task> Tasks { set;get;}
public UserViewModel()
{
Tasks=new List<UserTask>();
}
}
public class UserTask
{
public int ID { set;get;}
public string Name { set;get;}
public bool IsSelected { set;get;}
}
Now in your GET action, you create an object of UserViewModel and set the Tasks property to a list of available taks (from your database may be)
public ActionResult Add()
{
var vm=new UserViewModel();
vm.Tasks=GetAvailableTasks();
return View(vm);
}
Assuming GetAvailableTasks is a method which returns a list of UserTask objects.
Now create an editor templates. Go to your ~/Views/YourControllerName folder and create a folder called EditorTemplates. Add a new view to the newly created folder and give name as Task.cshtml. Add the below code to that
#model YourNameSpace.UserTask
<p>
#Html.CheckBoxFor(x => x.IsSelected) Model.Name
#Html.HiddenFor(x => x.ID)
</p>
Now go back to your main view and use EditorFor helper method to bring the EditorTemplate.
#model YourNamespace.UserViewModel
<h2>Quiz 24</h2>
#using (Html.BeginForm())
{
#Html.EditorFor(x=>x.Tasks)
<input type="submit" />
}
Now when the form gets submitted, you can get the selected checkbox values like this
[HttpPost]
public ActionResult Save(UserViewModel model)
{
List<int> taskIds=new List<int>();
foreach (var task in model.Tasks)
{
if (task.IsSelected)
{
//you can get the selected task id's here.
taskIds.Add(task.ID);
}
}
//to do : save and redirect (PRG pattern)
}
Here is a blog post explaining how to use EditorTemplates to handle collections on form submit. the post is showing an example to handle radio buttons.
You can actually skip all this rebuilding in the controller. Razor can rebuild the list of any object using a single form. You just need to fallback to the old for and assign a unique name, as well as include the primary key for that collection.
On postback razor will rebuild your ViewModel with the modified list without any extra work.
In the markup it looks something like this
And in the controller after posting back the model is rebuilt with the new data the user has entered, and also lazyBound to ForeignKey models!
Using checkboxes or radio inputs it's slightly different. The problem is that when a checkbox is not ticked, browsers do NOT submit it back, and the model will NOT rebuild. This is not a shortfall of VS but an intended implementation of HTML1 that still lives in HTML5!
If this happens the Index Out of Bounds will occur. So you have include a hidden field for checkboxes with a value of false before the Input Box (if a user checks it it will overwrite the false to true)
But if you are using a StronglyTyped model, like in my case, any check boxes evaluated to null are automatically set to false, known as the default value for a boolean as defined in the C# specification.

MVC 3 Model Binding without Key

I have a question about ASP.NET MVC3 model binding. If I have a class I'm trying to use as a model, but I don't want the key put on the page, the model doesn't bind on a POST. Here is an example:
//Data Model
public class MyModel
{
[Key]
public string MyKey {get;set;} //Perhaps this is an ssn that I don't want on the form.
public string MyValueToGet {get;set;} //This is the value I want the user to enter.
}
//Conroller code.
public ViewResult Index()
{
MyModel model = new MyModel{ MyKey = "SecretInfo", MyValueToGet = "" };
return View(new model);
}
public ActionResult Edit(MyModel model)
{
repository.SaveChanges(model)
}
//View code.
#using(Html.BeginForm("Edit", "Home", FormMethod.Post))
{
Enter a value: #Html.EditorFor(m => m.MyValueToGet)
<input type="submit" value="Salve" />
}
So my problem is that model is null when the Edit method is called upon form submission. I can fix this by putting MyKey somewhere on the page (perhaps as a hidden field), but that is unacceptable if it is some sort of sensitive data. Is there a way to solve this problem? I am new to MVC, so I appreciate any help.
Create another unique but otherwise meaningless identifier like an (auto increment int) and use that to bind.
in other words modify your model to something like:
public class MyModel
{
[Key]
public int ID {get; set;}
public string MyKey {get;set;} //Now this can be sensitive, it doesn't matter because you no longer rely on it.
public string MyValueToGet {get;set;} //This is the value I want the user to enter.
}
EDIT
I believe your best choice would be to change the MyModel object, as it's design is flawed. The primary key in the majority of cases (and I think this is one of them) should be a simple auto incrementing integer, meaningless apart from it's role as the table's key.
While Luke's suggestion to use Session is a viable option and a solution that would work, I would personally do something similar to what I'll explain here, as it would seem to me to be more of the 'mvc way' of doing things.
Data model:
Either change your current model to something like what I suggest above, or, if that is not feasible for whatever reason (breaking dependancies or FK relationships), create a new table that can be used as a join, or proxy, if you will:
public class Proxy
{
public int ProxyId {get;set;}
public MyModel MyModel {get; set;}
}
Obviously, you'd have to do some work to populate this table, but you would then be able to use it to fetch records from MyModel without accessing the MyKey property directly.
It's not considered good practice to use your data models directly in your views, so you want to create a view model as well
public class MyModelViewModel
{
public int ModelId {get; set;}
public string ModelValueToGet {get; set;}
}
Notice we don't even need the key containing sensitive data in the view model.
Then type your view to the viewModel, not the data model, and include a hidden field for the ModelId
#using(Html.BeginForm("Edit", "Home", FormMethod.Post))
{
Enter a value: #Html.EditorFor(m => m.ModelValueToGet)
#Html.HiddenFor(m => m.ModelId)
<input type="submit" value="Save" />
}
Now in your controller you have your get method
public ViewResult Index()
{
//fetch the users record from the database
//if you're using the Proxy table, you'll want to write a LINQ query here
//instantiate a viewModel and populate it's properties using the fetched record
//remember, the viewModel.ModelId should be set to MyModel.ID or Proxy.ProxyId
//render the view
}
And the post method
public ViewResult Edit (MyModelViewModel viewModel)
{
//fetch the users record from the database using viewModel.ModelId
//If you're using the proxy table, you'll need to use that LINQ query again here
//update the record you fetched with the new data the user just entered
//you have complete control here of what gets updated and what stays the same
//pass the updated record to the repository to save the changes.
//redirect the user to be on their merry way
}
I think that's about as well as I can lay it out. Hope it makes sense.
An alternative is to encrypt the id before sending it to the client. Check this post for more information on how to accomplish this.
Asp MVC 3: Modifiy Values Sent to View

ModelBinding a generic type in MVC3

In my views I'm using a generic type for the Model, ItemModel<T>.
This allows me to have a basetype on my model and it works fine. Within ItemModel<T> I attach the actual entity of T to a property called 'Item'.
Let's say I'm loading a User item: in my view I would like to do something like this:
<%: Html.TextBoxFor(Model => Model.Item.NickName,
new { id="NickName", name="NickName" })%>
Because the entity is added via the Item property, this generates the input tag's name attribute as 'Item.NickName'. When posting, MVC can no longer relate this to the User.NickName property of the user object in my controller
public ActionResult Login(User user, string redirectUrl)
and therefore nothing gets loaded into my User object. Is there a way to fix this? I've been looking into writing a custom bindingmodel or valueprovider, but that looks like alot of work for something this simple. Plus, I'm really not sure if that's that way to go.
All help is greatly appreciated!
change method of your actionresult from
public ActionResult Login(User user, string redirectUrl)
To
public ActionResult Login(User Item, string redirectUrl)
this way modelbiner will be able to locate properties of User object prefixed with Item
you can create your helper methods to create that textbox
public static MVCString MyTextBox<T>(this HtmlHelper html,T _item,String PropertyName)
{
TagBuilder myTag = new TagBuilder("input");
myTag.Attributes("type")="Text";
myTag.Attributes("id")=PropertyName;
myTag.Attributes("name")=PropertyName;
myTag.Attributes("value")=typeof(_item).GetProperty(PropertyName).GetValue(_item,null).toString();
Return MvcHtmlString.Create(selectTag.ToString())
}

How to change action parameters and get it to work without changing routing in asp.net mvc?

In my route I have something like this:
controller/action/{id}
To my knowledge this means it will call any action with the parameter id like the following:
public ActionResult Detail(string id)
{
}
What do I have to do to make the following work without registering the particular route in global.asax file:
public ActionResult Detail(string customerId)
{
}
If you really don't want to rename the method parameter, you can use BindAttribute to tell MVC what its logical name should be:
public ActionResult Detail([Bind(Prefix = "id")] string customerId)
You can also pass customerId as query string, which is normally what I do:
<%: Html.ActionLink("Detail", "Detail", new { #customerId = Model.CustomerID}, null)%>
MVC does not enforce the routing, but rather try to resolve routing based on your url AND query string.
have a route like - controller/action/{customerId} or just rename the parameter customerId to id and then use it the particular way you want.

Why is ListBoxFor not selecting items, but ListBox is?

I have the following code in my view:
<%= Html.ListBoxFor(c => c.Project.Categories,
new MultiSelectList(Model.Categories, "Id", "Name", new List<int> { 1, 2 }))%>
<%= Html.ListBox("MultiSelectList",
new MultiSelectList(Model.Categories, "Id", "Name", new List<int> { 1, 2 }))%>
The only difference is that the first helper is strongly typed (ListBoxFor), and it fails to show the selected items (1,2), even though the items appear in the list, etc. The simpler ListBox is working as expected.
I'm obviously missing something here. I can use the second approach, but this is really bugging me and I'd like to figure it out.
For reference, my model is:
public class ProjectEditModel
{
public Project Project { get; set; }
public IEnumerable<Project> Projects { get; set; }
public IEnumerable<Client> Clients { get; set; }
public IEnumerable<Category> Categories { get; set; }
public IEnumerable<Tag> Tags { get; set; }
public ProjectSlide SelectedSlide { get; set; }
}
Update
I just changed the ListBox name to Project.Categories (matching my model) and it now FAILS to select the item.
<%= Html.ListBox("Project.Categories",
new MultiSelectList(Model.Categories, "Id", "Name", new List<int> { 1, 2 }))%>
I'm obviously not understanding the magic that is happening here.
Update 2
Ok, this is purely naming, for example, this works...
<%= Html.ListBox("Project_Tags",
new MultiSelectList(Model.Tags, "Id", "Name", Model.Project.Tags.Select(t => t.Id)))%>
...because the field name is Project_Tags, not Project.Tags, in fact, anything other than Tags or Project.Tags will work. I don't get why this would cause a problem (other than that it matches the entity name), and I'm not good enough at this to be able to dig in and find out.
I've stumbled across this problem myself, finally I realized that the problem was a naming convention.
You cannot name the ViewBag or ViewData poperty containing the SelectList or MultiSelectList to the same name your property model containing the selected items. At least not if you're using the ListBoxFor or DropDownListFor helper.
Here's an example:
public class Person
{
public List<int> Cars { get; set; }
}
[HttpGet]
public ActionResult Create()
{
//wont work
ViewBag.Cars = new SelectList(carsList, "CarId", "Name");
//will work due to different name than the property.
ViewBag.CarsList = new SelectList(carsList, "CarId", "Name");
return View();
}
//View
#Html.ListBoxFor(model => model.Cars, ViewBag.CarsList as SelectList)
I'm sure theres plenty of other ways doing this, but it solved my problem, hope it will help someone!
I have also been stuck with this exact same issue and encountered the same problem with ListBox and ListBoxFor.
No matter what I do, I cannot get selections to occur on the ListBoxFor. If I change to the ListBox and name it something OTHER than the property name of the data I am binding to, selections occur.
But then because I'm not using ListBoxFor and the data is sitting inside a model class (Model.Departments) for example, I don't get model binding on the way back to my controller and hence the property is null.
EDIT I found a solution posted by someone else here;
Challenges with selecting values in ListBoxFor
Also, you can try to clear ModelState for c.Project.Categories in the controller:
[HttpPost]
public ActionResult Index(ModelType model)
{
ModelState.Remove("Project.Categories");
return View("Index", model);
}
And use the next construction:
<%= Html.ListBoxFor(c => c.Project.Categories,
new MultiSelectList(Model.Categories, "Id", "Name"))%>
Where c.Project.Categories is IEnumerable<int>.
Sorry for my english. Good luck!
The correct answer is that it doesn't work very well. As such I read the MVC code. What you need to do is implement IConvertible and also create a TypeConverter.
So, in my instance I had a Country class, such that people could choose from a list of countries. No joy in selecting it. I was expecting an object equals comparison on the selectedItems against the listitems but no, that's not how it works. Despite the fact that MultiListItem works and correctly gets the selected items, the moment it is bound to your model it's all based on checking that the string represnetation of your object instance matches the string "value" (or name if that is missing) in the list of items in the SelectItemList.
So, implement IConvertible, return the string value from ToString which would match the value in the SelectItemList. e.g in my case CountryCode was serialized into the SelectItem Value property , so in ToString IConvertible I returned CountryCode. Now it all selects correctly.
I will point out the TypeConverter is used on the way in. This time its the inverse. That Countrycode comes back in and "EN" needs converting into Country class instance. That's where the TypeConverter came in. It's also about the time I realised how difficult this approach is to use.
p.s so on your Category class you need to implement IConvertible. If its from the entity framework as my company is then you'll need to use the partial class to implement IConvertible and implement ToString and decorate it with a TypeConverter you wrote too.
Although this isn't an answer to your main question, it is worth noting that when MVC generates names it will turn something like Project.Tags into Project_Tags, replacing periods with underscores.
The reason that it does this is because a period in an element ID would look like an element named Project with a class of Tags to CSS. Clearly a bad thing, hence the translation to underscores to keep behaviour predictable.
In your first example,
<%= Html.ListBoxFor(c => c.Project.Categories,
new MultiSelectList(Model.Categories, "Id", "Name", new List<int> { 1, 2 }))%>
the listbox is attempting to bind to Model.Project.Categories for your strongly typed Model which has been provided to the page (using the lambda notation). I'm not sure what the second parameter in the ListBoxFor is doing though.
What is the Model that is being passed to the page?
Try this
<%= Html.ListBoxFor(c => c.Project.Categories,
new MultiSelectList(
Model.Categories
,"Id"
,"Name"
,Model.Project.Tags.Select(
x => new SelectListItem()
{
Selected = true,
Text = x.TEXT,
Value = x.ID.ToString()
}).ToList())
)
)%>
Html.ListboxFor and Html.Listbox work great when you're NOT binding the list box to its data source. I assume the intended use is this:
// Model
public class ListBoxEditModel
{
public IEnumerable<Category> Categories { get; set; }
public IEnumerable<Category> SelectedCategories { get; set; }
}
In the view:
#Html.ListBoxFor(m => m.SelectedCategories,
new MultiSelectList(Model.Categories, "Id", "Name"))
// or, equivalently
#Html.ListBox("SelectedCategories" ,
new MultiSelectList(Model.Categories, "Id", "Name"))
Note that in this case you don't have to explicitly say which values are selected in the MultiSelectList - the model you're binding to takes precedence, even if you do!

Resources