How can I make Html.DropDownList encode its values? - c#

In my viewData I have an IList mls.
I want to use this to show in a dropdown. Like so:
<%= Html.DropDownList("ml3Code",
new SelectList(Model.Mls, "Code", "Description", Model.Ml3.Code ?? ""),
Model.T9n.TranslateById("Labels.All"),
new { #class = "searchInput" })%>
This works fine, until there's a myObject.Code == VOC<420 g/l.
I would have expected that an HTML helper would encode its values, but it doesn't.
How should I approach this problem? The only thing I can come up with is first making a dupe list of the objects with encoded values and then feeding it to the selectlist. This would be really bothersome.
P.S. I hope Phill H. and his team will have a long and thorough look at the encoding for asp.net-mvc 2.0...

I'm puzzled. The question "Do ASP.NET MVC helper methods like Html.DropDownList() encode the output HTML?" was asked on SO before, and the answer was "Yes" - and the source-code from the MVC framework was cited to back this assertion up.

Well, you can roll your own Html helper, but if you're like me you won't want to do that.
To me, I see two options here:
Write your select element in plain view without the helper. I've never felt the helpers provide you much save for highlighting an element when an error occurs.
Patch the select box on the client when the page loads, as in:
function encodeHtml(str)
{
var encodedHtml = escape(str);
encodedHtml = encodedHtml.replace(///g,"%2F");
encodedHtml = encodedHtml.replace(/\?/g,"%3F");
encodedHtml = encodedHtml.replace(/=/g,"%3D");
encodedHtml = encodedHtml.replace(/&/g,"%26");
encodedHtml = encodedHtml.replace(/#/g,"%40");
return encodedHtml;
}
window.onload = function()
{
var ml3Code = document.getElementById("ml3Code");
for(var i = 0; i < ml3Code.options.length; ++i)
{
ml3Code.options[i].value = encodeHtml(ml3Code.options[i].value);
}
};
It's a hack, I know. I strongly prefer the first choice.

This is encoded. But dont check with firebug - It shows values decoded.
Check in ViewSource of the Browser and things are encoded.
Controller
public List<CategoryInfo> GetCategoryList()
{
List<CategoryInfo> categories = new List<CategoryInfo>();
categories.Add(new CategoryInfo { Name = "Food<äü", Key = "VOC<420 g/l", ID = 2, Uid = new Guid("C0FD4706-4D06-4A0F-BC69-1FD0FA743B07") });
}
public ActionResult Category(ProductViewModel model )
{
IEnumerable<SelectListItem> categoryList =
from category in GetCategoryList()
select new SelectListItem
{
Text = category.Name,
Value = category.Key
};
model.CategoryList = categoryList;
return View(model);
}
View
<%= Html.DropDownList("Category" , Model.CategoryList) %>
Model
public class ProductViewModel
{
public IEnumerable<SelectListItem> CategoryList { get; set; }
public List<CategoryInfo> Categories { get; set; }
}
HTML
<select id="Category" name="Category"><option value="VOC<420 g/l">Food<äü</option>
</select>

Related

MVC Model Binding Dynamic List of Lists

I have a dynamic list of dynamic lists, which have <input />s that need to be POSTed to an MVC controller/action and bound as a typed object. The crux of my problem is I can't figure out how to manually pick out arbitrary POSTed form values in my custom model binder. Details are below.
I have a list of US States that each have a list of Cities. Both States and Cities can be dynamically added, deleted, and re-ordered. So something like:
public class ConfigureStatesModel
{
public List<State> States { get; set; }
}
public class State
{
public string Name { get; set; }
public List<City> Cities { get; set; }
}
public class City
{
public string Name { get; set; }
public int Population { get; set; }
}
The GET:
public ActionResult Index()
{
var csm = new ConfigureStatesModel(); //... populate model ...
return View("~/Views/ConfigureStates.cshtml", csm);
}
The ConfigureStates.cshtml:
#model Models.ConfigureStatesModel
#foreach (var state in Model.States)
{
<input name="stateName" type="text" value="#state.Name" />
foreach (var city in state.Cities)
{
<input name="cityName" type="text" value="#city.Name" />
<input name="cityPopulation" type="text" value="#city.Population" />
}
}
(There is more markup and javascript, but I leave it out for brevity/simplicity.)
All form inputs are then POSTed to server, as so (parsed by Chrome Dev Tools):
stateName: California
cityName: Sacramento
cityPopulation: 1000000
cityName: San Francisco
cityPopulation: 2000000
stateName: Florida
cityName: Miami
cityPopulation: 3000000
cityName: Orlando
cityPopulation: 4000000
I need to capture the form values, ideally bound as a List<State> (or, equivalently, as a ConfigureStatesModel), as so:
[HttpPost]
public ActionResult Save(List<State> states)
{
//do some stuff
}
A custom model binder seems like the right tool for the job. But I don't know how to know which city names and city populations belong to which state names. That is, I can see all the form keys and values POSTed, but I don't see a way to know their relation:
public class StatesBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
//California, Florida
List<string> stateNames = controllerContext.HttpContext.Request.Form.GetValues("stateName").ToList();
//Sacramento, San Francisco, Miami, Orlando
List<string> cityNames = controllerContext.HttpContext.Request.Form.GetValues("cityName").ToList();
//1000000, 2000000, 3000000, 4000000
List<int> cityPopulations = controllerContext.HttpContext.Request.Form.GetValues("cityPopulation")
.Select(p => int.Parse(p)).ToList();
// ... build List<State> ...
}
}
If I could just know the order all values came in in relation to all other form values, that would be enough. The only way I see to do this is looking at the raw request stream, as so:
Request.InputStream.Seek(0, SeekOrigin.Begin);
string urlEncodedFormData = new StreamReader(Request.InputStream).ReadToEnd();
but I don't want to be messing with manually parsing that.
Also note that the order of the list of states and the order of the lists of cities in each state matter, as I persist the concept of display-order for them. So that would need to be preserved from the form values as well.
I've tried variations of dynamic list binding like this and this. But it feels wrong junking up the html and adding a lot of (error-prone) javascript, just to get the binding to work. The form values are already there; it should just be a matter of capturing them on the server.
The only obvious way I see of building a form that will actually represent which cities belong to which state would require that you use the strongly-typed helpers.
So, I'd use something similar to:
#model Models.ConfigureStatesModel
#for (int outer = 0; outer < Model.States.Count; outer++)
{
<div class="states">
#Html.TextBoxFor(m => m.States[outer].Name, new { #class="state" })
for (int inner = 0; inner < Model.States[outer].Cities.Count; inner++)
{
<div class="cities">
#Html.TextBoxFor(m => m.States[outer].Cities[inner].Name)
#Html.TextBoxFor(m => m.States[outer].Cities[inner].Population)
</div>
}
</div>
}
This will create inputs with form names that the default modelbinder can handle.
The part that requires some additional work is handling the re-ordering. I would use something like this, assuming you are using jQuery already:
// Iterate through each state
$('.states').each(function (i, el) {
var state = $(this);
var input = state.find('input.state');
var nameState = input.attr('name');
if (nameState != null) {
input.attr('name', nameState.replace(new RegExp("States\\[.*\\]", 'gi'), '[' + i + ']'));
}
var idState = input.attr('id');
if (idState != null) {
input.attr('id', idState.replace(new RegExp("States_\\d+"), i));
}
// Iterate through the cities associated with each state
state.find('.cities').each(function (index, elem) {
var inputs = $(this).find('input');
inputs.each(function(){
var cityInput = (this);
var nameCity = cityInput.attr('name');
if (nameCity != null) {
cityInput.attr('name', nameCity.replace(new RegExp("Cities\\[.*\\]", 'gi'), '[' + index + ']'));
}
var idCity = cityInput.attr('id');
if (idCity != null) {
cityInput.attr('id', idCity.replace(new RegExp("Cities_\\d+"), index));
}
});
});
});
This last bit probably requires some tweaking, as it's untested, but it's similar to something I've done before. You would call this whenever the items on your view are added/edited/removed/moved.
I came up with my own solution. It's a little bit of a hack, but I feel it's better than the alternatives. The other solution and suggestions all involved altering the markup and adding javascript to synchronize the added markup -- which I specifically said I did not want to do in the OP. I feel adding indexes to the <input /> names is redundant if said <input />s are already ordered in the DOM the way you want them. And adding javascript is just one more thing to maintain, and unnecessary bits sent through the wire.
Anyways .. My solution involves looping through the raw request body. I hadn't realized before that this is basically just a url-encoded querystring, and it's easy to work with after a simple url-decode:
public class StatesBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
controllerContext.HttpContext.Request.InputStream.Seek(0, SeekOrigin.Begin);
string urlEncodedFormData = new StreamReader(controllerContext.HttpContext.Request.InputStream).ReadToEnd();
var decodedFormKeyValuePairs = urlEncodedFormData
.Split('&')
.Select(s => s.Split('='))
.Where(kv => kv.Length == 2 && !string.IsNullOrEmpty(kv[0]) && !string.IsNullOrEmpty(kv[1]))
.Select(kv => new { key = HttpUtility.UrlDecode(kv[0]), value = HttpUtility.UrlDecode(kv[1]) });
var states = new List<State>();
foreach (var kv in decodedFormKeyValuePairs)
{
if (kv.key == "stateName")
{
states.Add(new State { Name = kv.value, Cities = new List<City>() });
}
else if (kv.key == "cityName")
{
states.Last().Cities.Add(new City { Name = kv.value });
}
else if (kv.key == "cityPopulation")
{
states.Last().Cities.Last().Population = int.Parse(kv.value);
}
else
{
//key-value form field that can be ignored
}
}
return states;
}
}
This assumes that (1) the html elements are ordered on the DOM correctly, (2) are set in the POST request body in the same order, and (3) are received in the request stream on the server in the same order. To my understanding, and in my case, these are valid assumptions.
Again, this feels like a hack, and doesn't seem very MVC-y. But it works for me. If this happens to help someone else out there, cool.

Binding the dropdownlist to the mvc view

I am trying to bind the dropdown list to the data-set coming from the data context class in mvc 6. I wrote a function to get the populated list but unable to reproduce the same using razor. Here's what I have so far. Please note that I have not created a model yet. trying to make use of the generated POCO class from the database scaffolding.
function on Layout.cshtml
#functions{
public List<HSIP.Entities.StateDetails> function1()
{
// protected readonly HSIP.Entities.HSIPContext context;
HSIP.Entities.HSIPContext hsipcontext = new HSIP.Entities.HSIPContext();
List<HSIP.Entities.StateDetails> getstatelist = (from s in hsipcontext.StateDetails
select new HSIP.Entities.StateDetails
{
StateDesc = s.StateDesc,
StateCode = s.StateCode,
StateAbbr = s.StateAbbr
}).ToList();
//SelectList list = new SelectList(getstatelist, "Region", "StateCode", "StateAbbr", "StateDesc");
return getstatelist;
}
}
Razor syntax:
#Html.DropDownList("StateDesc", #function1(), "Please select State Name");
The Razor syntax throws an error: there is no argument given that corresponds to the required formal parameter 'htmlattributes' of IHTMLHelper.Dropdownlist(string, IEnumerable, string, object).
can someone please point me in the right direction.
Thanks,
Hari
I am prefer do this:
In a controller/Model:
using System.Web.Mvc;
public List<SelectListItem> DropdownListFilter()
{
var listitem = new List<SelectListItem>();
listitem.Add(new SelectListItem { Text = "Dropdown1", Value = "0", Selected = true });
listitem.Add(new SelectListItem { Text = "Dropdown2", Value = "1", Selected = false });
listitem.Add(new SelectListItem { Text = "Dropdown3", Value = "2", Selected = false });
return listitem;
}
When I Load in the ActionResult Just add this following Line:
ViewBag.FilterDropdown = ar.DropdownListFilter().ToList();
And in the view you have to call Filter dropdown like this:
#Html.DropDownList("FilterDropdown")
Hope this help.
Firstly use a SelectListItem in your controller and pass it to your view.Then use it in Razor syntax to populate the dropdown.
List<SelectListItem> stateList = (from s in hsipcontext.StateDetails
select new HSIP.Entities.StateDetails
{
StateDesc = s.StateDesc,
StateCode = s.StateCode,
StateAbbr = s.StateAbbr
}).ToList();
View:
#Html.DropDownListFor("StateDesc", stateList ,"Please select State Name")

Public void method not working. Cannot implicitly convert type 'void' to 'object'

I've googled about but still haven't found what I'm looking for. What I want to happen is to get all the comments for a particular image that's been posted.
#model IEnumerable<Project1.Models.Picture>
#{
Layout = "~/Views/Shared/_PictureLayout.cshtml";
ViewBag.Title = "Animal Pictures";
}
#foreach (var picture in Model)
{
long id = picture.PictureID;
<div class="picture">
#Html.ActionLink("Picture", "IndexPic", "Pictures", new { id = picture.PictureID
}, true)
<img src="" alt="#picture.File" />
Posted by #Html.ActionLink(picture.GetUsername(picture.UserID), "Index",
"Pictures", new { username = picture.GetUsername(picture.UserID) }, true)
at #picture.Posted
#picture.GetComments(picture.PictureID) **** HERE LIES THE PROBLEM!!!
</div>
}
The error that is returned is Cannot implicitly convert type 'void' to 'object'
The plan was to get the pictures ID and pass it to a method which would then get all the comments for that picture
public void GetComments(long pictureID)
{
DBContext db = new DBContext();
Picture picture = new Picture();
//PictureComment comments = new PictureComment();
var comments = from c in db.PictureComments
where pictureID == c.PictureID
orderby c.DateTime descending
select c;
foreach (var comment in comments)
{
Console.WriteLine(picture.GetUsername(comment.UserID));
Console.WriteLine(comment.Comment);
}
}
I thought at first it was to do with being in a foreach loop, however the picture.GetUsername() method works fine.
Are there any SIMPLE work around, I say simple because I am new to c# and not aware of all the concepts/terminology.
Thanks.
You shouldn't use Console.WriteLine and your method should return a MvcHtmlString.
When you use the # symbol before the method in your cshtml file, this means that the result from the method will be written in the resulting html.
this should work:
public MvcHtmlString GetComments(long pictureID)
{
DBContext db = new DBContext();
Picture picture = new Picture();
//PictureComment comments = new PictureComment();
var comments = from c in db.PictureComments
where pictureID == c.PictureID
orderby c.DateTime descending
select c;
StringBuilder sb = new StringBuilder();
foreach (var comment in comments)
{
sb.AppendLine(picture.GetUsername(comment.UserID));
sb.AppendLine(comment.Comment);
}
return new MvcHtmlString(sb.ToString());
}
This would solve your problem, but I suppose you would like to format the comments in some way in your html, so the best thing to do here is to return the list of your comments for a picture.
Then in your cshtml file, use a foreach loop to iterate through them, and format them properly with the needed html.

How to page with Custom method/ActionLink?

I am probably going about this completely wrong here, but that is partly what I am asking.
I am creating a blog using MVC3 and I am having some issues. My homepage currently lists each blog post with their corresponding comments and topics correctly. I want it to be limited to a number of posts, so here is my code in the HomeController.
public class HomeController : Controller
{
private MyDB db = new MyDB();
public ActionResult Index()
{
var posts = (from p in db.Set<BlogPost>()
orderby p.DateCreated descending
select new PostViewModel
{
Title = p.Title,
DateCreated = p.DateCreated,
Content = p.Content,
Topics = p.Topics,
Comments = p.Comments,
CommentCount = p.Comments.Count
}).Take(5).ToList();
IEnumerable<Topic> topics = from t in db.Topics
select t;
var blog = new BlogViewModel
{
Post = posts,
Topics = topics.Select(t => new SelectListItem {
Value = Convert.ToString(t.id),
Text = t.Name
})
};
return View(blog);
}
}
This works fine as I've said. I have the topics coming in separately because I want to eventually sort by those (which I also don't know where to start but that's another story).
My main problem is that I would like to have a "Next" and "Previous" button under the 5 selected posts, that when clicked, grab the next 5 or previous 5. I've been told to use...
#Html.ActionLink("Next >>", "ActionName", "Home", Custom arguement?)
type of solution where I write a custom method in my HomeController and grab the next or previous 5. Is this at all correct? I'd like to understand the best use scenario for something like this. I am completely new to MVC3, so I am not looking for shortcuts, and I feel like I maybe already have made a few.
Thanks for your help.
https://github.com/TroyGoode/PagedList
public class HomeController : Controller
{
public ActionResult Index(int? page)
{
var posts = (from p in db.Set<BlogPost>()
orderby p.DateCreated descending
select new PostViewModel
{
Title = p.Title,
DateCreated = p.DateCreated,
Content = p.Content,
Topics = p.Topics,
Comments = p.Comments,
CommentCount = p.Comments.Count
});
var pageNumber = page ?? 1; // if no page was specified in the querystring, default to the first page (1)
posts = posts.ToPagedList(pageNumber, 25); // will only contain 25 posts max because of the pageSize
var blog = new BlogViewModel
{
Post = posts,
Topics = topics.Select(t => new SelectListItem {
Value = Convert.ToString(t.id),
Text = t.Name
})
};
return View(blog);
}
}
I see two best scenarios:
One you could use Ajax/Jquery and do this.
Two you use a 'Partial' View. This way you are not affecting the ENTIRE view when someone presses the action link for the next five. So you would make a partial view for just the posts, then the controller receives a request for the next five and returns a partial view with a model that has five post items.
Choose which one you feel more comfortable with, but if you are learning MVC, choice two may be better for you for practice.
You can take a look at: http://nerddinnerbook.s3.amazonaws.com/Part8.htm .
Also if you want to get the data with an ajax request i recommend you to convert partial view containing the data to string ( Return Razor partial view using JSON (ASP MVC 3)) and use jquery to load it (http://api.jquery.com/load/ ).
I'd request another action method on your controller (either with Ajax, or normal), and just reuse the same View you have been using for your initial page render.
public ActionResult Index(int? pageNumber)
{
if(!pageNumber.HasValue)
return Index();
var posts = ...; // get posts
var set = posts.Skip(pageNumber * itemsPerPage).Take(itemsPerPage);
// or pageNumber - 1 if you want to be 1-index based
return View(...); //or PartialView() if doing ajax, or even Json() if you want to bind on the client side
}
You could create your Controller like the example code below:
public ViewResult List(string category, int page = 1) {
ProductsListViewModel viewModel = new ProductsListViewModel {
Products = repository.Products
.Where(p => category == null ? true : p.Category == category)
.OrderBy(p => p.ProductID)
.Skip((page - 1) * PageSize)
.Take(PageSize),
PagingInfo = new PagingInfo {
CurrentPage = page,
ItemsPerPage = PageSize, //4 for example
TotalItems = category == null ?
repository.Products.Count() :
repository.Products.Where(e => e.Category == category).Count()
},
CurrentCategory = category
};
return View(viewModel);
}
Inside your View, you put this custom HTML Helper:
<div class="pager">
#Html.PageLinks(Model.PagingInfo, x => Url.Action("List",
new {page = x, category = Model.CurrentCategory}))
</div>
Here is the code of the Helper:
public static class PagingHelpers {
public static MvcHtmlString PageLinks(this HtmlHelper html,
PagingInfo pagingInfo,
Func<int, string> pageUrl) {
StringBuilder result = new StringBuilder();
for (int i = 1; i <= pagingInfo.TotalPages; i++) {
TagBuilder tag = new TagBuilder("a"); // Construct an <a> tag
tag.MergeAttribute("href", pageUrl(i));
tag.InnerHtml = i.ToString();
if (i == pagingInfo.CurrentPage)
tag.AddCssClass("selected");
result.Append(tag.ToString());
}
return MvcHtmlString.Create(result.ToString());
}
}
This example was taken from this book

Issue with MVC2 DropDownListFor, only displaying the class-name

Good day,
I have this problem with Html.DropDownListFor which I can't seem to work out.. It gives me the correct number of options, if I do a breakpoint in the code where it is supposed to render the list, the "SelectItemList" object contains the items with correct values, but the actual HTML it spits out is the following:
<select id="Reason_ReasonId" name="Reason.ReasonId"><option>System.Web.Mvc.SelectListItem</option>
<option>System.Web.Mvc.SelectListItem</option>
<option>System.Web.Mvc.SelectListItem</option>
<option>System.Web.Mvc.SelectListItem</option>
<option>System.Web.Mvc.SelectListItem</option>
<option>System.Web.Mvc.SelectListItem</option>
</select>
The module containts this:
public SelectList ReasonList
{
get
{
SelectList selectList;
List<SelectListItem> selectItems;
using (var db = new EscalationDataContext())
{
var reasons =
db.Reasons
.OrderBy(r => r.Reason1)
.Select(r => new SelectListItem
{
Value = r.ReasonId.ToString(),
Text = r.Reason1
});
selectItems = reasons.ToList();
selectList = new SelectList(selectItems);
}
return selectList;
}
}
The controller just creates a default instantiation and sets the default value:
public ActionResult Create()
{
EscalationModel model = new EscalationModel
{
Reason = new Reason { ReasonId = new Guid("598c28c2-877a-44fa-9834-3241c5ee9355"), Reason1 = "Taken too long" },
ActionedDate = DateTime.Now
};
return View(model);
}
Last but not least, the view:
<%: Html.DropDownListFor(m => m.Reason.ReasonId, Model.ReasonList) %>
Any ideas why it behaves like this? As I said, in the actual code (in the view) I have the correct values, but.. It doesn't like me.. Any help would be greatly appreciated, thanks in advance!
Ok.. It seems you had to specify which variable in SelectListItem was used for "Value" and which was used for "Text"..
selectList = new SelectList(selectItems);
Became..
selectList = new SelectList(selectItems, "Value", "Text");
Which seems to have done the trick!

Categories