I'm trying to dynamically set the disabled attribute for the TextBoxFor HtmlHelper
#Html.TextBoxFor(model => model.Street,
new
{
#class = "",
disabled = (Model.StageID==(int)MyEnum.Sth) ? "disabled" : ""
})
but even if there is disabled="" it is the same as disabled="disabled". How to get around of this ?
I have the same problem about month ago and I finished by using this extension method for it
public static class AttributesExtensions
{
public static RouteValueDictionary DisabledIf(
this object htmlAttributes,
bool disabled
)
{
var attributes = new RouteValueDictionary(htmlAttributes);
if (disabled)
{
attributes["disabled"] = "disabled";
}
return attributes;
}
}
And after that you can use it like this
#Html.TextBoxFor(
model => model.Street,
new { #class = "" }.DisabledIf(Model.StageID==(int)MyEnum.Sth)
)
EDIT (after Paul's comment):
The using of data-xxx html attributes may be mined by using the constructor of the System.Web.Routing.RouteValueDictionary class, since underscores will not be automatically converted to minus sign.
Use the method System.Web.Mvc.HtmlHelper.AnonymousObjectToHtmlAttributes instead: will solve this issue.
UPDATED CODE (Extension method body only)
var attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
if (disabled)
{
attributes["disabled"] = "disabled";
}
return attributes;
Using the extension method below produces similar results, but this is perhaps more fragile:
#Html.TextBoxFor(
model => model.Street,
new { #class = "form-control" }
).DisabledIf(Model.IsReadOnly)
Extension:
using System.Text.RegularExpressions;
using System.Web.Mvc;
namespace xxx.HtmlHelpers
{
public static class MvcHtmlStringExtensions
{
private static readonly Regex OpeningTagPattern;
static MvcHtmlStringExtensions()
{
OpeningTagPattern = new Regex("<[a-zA-Z]*");
}
public static MvcHtmlString DisabledIf(this MvcHtmlString controlHtml, bool isDisabled)
{
if (!isDisabled) return controlHtml;
return
new MvcHtmlString(OpeningTagPattern.Replace(controlHtml.ToString(),
x => string.Format("{0} disabled=\"disabled\"", x.Groups[0])));
}
}
}
Probably your stage Id is not getting set
#{
if(Model.StageID != null && Model.StageID > 0)
{
#Html.TextBoxFor(model => model.Street,
new
{
#class = "",
disabled = (Model.StageID==(int)MyEnum.Sth) ? "disabled" : ""
})
}else{
#Html.TextBoxFor(model => model.Street,
new
{
#class = ""
})
}
}
We actually just ran into the same problem. We ended up implementing an extension method with overloaded parameters, which takes in a boolean indicating whether or not we want the control disabled. We just add the "disabled" attribute when appropriate, and let the built-in HtmlHelper handle the heavy lifting.
Extension class and method:
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;
public static class OurHtmlHelpers
{
public const string DisabledAttribute = "disabled";
public static MvcHtmlString TextBoxFor<TModel, TProp>(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProp>> expression,
object htmlAttributes,
bool canEdit)
{
var htmlAttributeDictionary = SetDisabledAttribute(htmlAttributes, canEdit);
return htmlHelper.TextBoxFor(expression, htmlAttributeDictionary);
}
private static RouteValueDictionary SetDisabledAttribute(object htmlAttributes, bool canEdit)
{
var htmlAttributeDictionary = new RouteValueDictionary(htmlAttributes);
if (!canEdit)
{
htmlAttributeDictionary.Add(DisabledAttribute, DisabledAttribute);
}
return htmlAttributeDictionary;
}
}
Then you just need to reference your new class and call #Html.TextBoxFor(m => m.SomeValue, new { #class = "someClass" }, <Your bool value>)
It's worth noting that you'd have to define these extensions for any of the TextBoxFor overloads you'd like to use, but it seems like a reasonable trade off. You can also utilize most of the same code for other HtmlHelpers you'd like to add the functionality to.
Related
Writing a small proof of concept application and wondering why the masterPath parameter is empty:
in application_start:
ViewEngines.Engines.Add(new AlternateLocationViewEngine(
new string[] {
"~/Views/Shared/_Layout.cshtml", //Is this correct? Can/should i do that
"~/Views/Shared/{0}.cshtml",
"~/Plugins/Views/Shared/{0}.cshtml",
},
new string[] {
"~/Plugins/Views/{1}/{0}.cshtml",
"~/Plugins/{1}/{0}.chstml",
"~/Plugins/Views/Shared/{0}.cshtml"
}
));
public class AlternateLocationViewEngine : RazorViewEngine
{
public AlternateLocationViewEngine(string[] masterLocations, string[] viewLocations)
: base()
{
MasterLocationFormats = masterLocations;
ViewLocationFormats = viewLocations;
PartialViewLocationFormats = ViewLocationFormats;
}
protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
if (string.IsNullOrEmpty(masterPath))
{
masterPath = MasterLocationFormats.ElementAt(0);
}
var nameSpace = controllerContext.Controller.GetType().Namespace;
return base.CreateView(controllerContext, viewPath.Replace("%1", nameSpace), masterPath.Replace("%1", nameSpace));
}
}
As you see i 'm forced to check if masterPath is empty in method CreateView(). Why is this? Am i missing something fundamental?
My dev environment: ASP.NET MVC3, Razor, .NET4
The masterPath will only have a value when creating a ViewResult with a masterName.
protected internal ViewResult View(string viewName, string masterName);
Internally, the RazorView handles null masterPaths in it's constructor.
// where layoutPath is the masterPath arg from the RazorViewEngine's CreateView
LayoutPath = layoutPath ?? String.Empty;
When rendering the view, the RazorView will set the OverridenLayoutPath to the masterPath (if supplied).
// An overriden master layout might have been specified when the ViewActionResult got returned.
// We need to hold on to it so that we can set it on the inner page once it has executed.
webViewPage.OverridenLayoutPath = LayoutPath;
You do not need to specify the _Layout as one of the MasterLocationFormats. Below is the default behavior for the RazorViewEngine.
MasterLocationFormats = new[] {
"~/Views/{1}/{0}.cshtml",
"~/Views/{1}/{0}.vbhtml",
"~/Views/Shared/{0}.cshtml",
"~/Views/Shared/{0}.vbhtml"
};
You can checkout the source code for more inspiration.
i crate #Html.ActionLink helper here i check permissions of user. if yes i show this link otherwise not. now problem is with #Ajax.ActionLink can i make helper for Ajax.ActionLink? i make custom helpers to check permissions. it works fine with html.actionlink helper. How i check permissions in ajax actions ?
public static IHtmlString CustomActionLink(this HtmlHelper htmlHelper, int userId, string reqController, string reqAction, string linkText,int reqActionId = 0)
{
bool isAllowed = checkPermission(userId, reqController, reqAction, reqActionId);
if (isAllowed == false)
{
return MvcHtmlString.Empty;
}
return htmlHelper.ActionLink(linkText, reqAction, new { id =reqActionId });
}
i want to do this same check in Ajax Actions.
In ASP.NET MVC HTML helper methods are just extension methods to the existing HtmlHelper and AjaxHelper classes. Once you understand what an extension method is in .NET and how it works, it's not that difficult to apply this concept to the AjaxHelper class:
public static IHtmlString CustomAjaxActionLink(
this AjaxHelper ajaxHelper,
AjaxOptions ajaxOptions,
int userId,
string reqController,
string reqAction,
string linkText,
int reqActionId = 0
)
{
bool isAllowed = checkPermission(userId, reqController, reqAction, reqActionId);
if (!isAllowed)
{
return MvcHtmlString.Empty;
}
return ajaxHelper.ActionLink(
linkText,
reqAction,
new { id = reqActionId },
ajaxOptions
);
}
And inside your view simply use this custom helper (after bringing the namespace into which the containing class is declared into scope of course):
#Ajax.CustomAjaxActionLink(
new AjaxOptions { UpdateTargetId = "foo" },
123,
"SomeController",
"SomeAction",
"click me and get a surprise!",
456
)
How about using AuthorizeAttribute?
public class AuthorizeAdminAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
if(!AppSecurity.Instance.IsUserInRoles(filterContext.HttpContext.User, AdminGroups))
{
HandleUnauthorizedRequest(filterContext);
}
base.OnAuthorization(filterContext);
}
}
and in your Controller you can use something like:
[AuthorizeAdmin]
public ActionResult Index()
{
return View();
}
In the _Layout.cshtml file, I have a section at the bottom of the body called "ScriptsContent" declared like this:
#RenderSection("ScriptsContent", required: false)
In my view, I can then use this section to add scripts to be executed. But what if I also have a PartialView that also need to use this section to add additional scripts?
View
#section ScriptsContent
{
<script type="text/javascript">
alert(1);
</script>
}
#Html.Partial("PartialView")
PartialView
#section ScriptsContent
{
<script type="text/javascript">
alert(2);
</script>
}
Result
Only the first script is rendered. The second script doesn't exist in source code of the webpage.
Razor seems to only output the first #section ScriptsContent that it sees. What I would like to know is if there's a way to merge each call to the section.
If we cannot do this, what do you propose?
Here's a solution for that problem. It's from this blog: http://blog.logrythmik.com/post/A-Script-Block-Templated-Delegate-for-Inline-Scripts-in-Razor-Partials.aspx
public static class ViewPageExtensions
{
private const string SCRIPTBLOCK_BUILDER = "ScriptBlockBuilder";
public static MvcHtmlString ScriptBlock(this WebViewPage webPage, Func<dynamic, HelperResult> template)
{
if (!webPage.IsAjax)
{
var scriptBuilder = webPage.Context.Items[SCRIPTBLOCK_BUILDER] as StringBuilder ?? new StringBuilder();
scriptBuilder.Append(template(null).ToHtmlString());
webPage.Context.Items[SCRIPTBLOCK_BUILDER] = scriptBuilder;
return new MvcHtmlString(string.Empty);
}
return new MvcHtmlString(template(null).ToHtmlString());
}
public static MvcHtmlString WriteScriptBlocks(this WebViewPage webPage)
{
var scriptBuilder = webPage.Context.Items[SCRIPTBLOCK_BUILDER] as StringBuilder ?? new StringBuilder();
return new MvcHtmlString(scriptBuilder.ToString());
}
}
so anywwhere in your View or PartialView you can use this:
#this.ScriptBlock(
#<script type='text/javascript'>
alert(1);
</script>
)
and in your _Layout or MasterView, use this:
#this.WriteScriptBlocks()
There is no way to share sections between a view and partial views.
Absent a ScriptManager-like solution, you could have a collection of script files (initialized in your view and stored either in HttpContext.Items or in ViewData) to which the partial view would append the script file names it requires. Then towards the end of your view you would declare a section that fetches that collection and emits the right script tags.
The problem with the accepted answer is that it breaks Output Caching. The trick to solving this is to overwrite the OutputCache attribute with your own implementation. Unfortunately we can't extend the original attribute since it has lots of internal methods which we need to access.
I actually use Donut Output Caching which overwrites the OutputCache attribute itself. There are alternative libraries which also use their own OutputCache attribute so I will explain the steps I made to get it to work so that you can apply it to whichever one you're using.
First you need to copy the existing OutputCache attribute and place it within your application. You can get the existing attribute by looking at the source code.
Now add the following property to the class. This is where we store the script blocks so we can render the correct ones when retrieving from the cache.
public static ConcurrentDictionary<string, StringBuilder> ScriptBlocks = new ConcurrentDictionary<string, StringBuilder>();
Now inside the OnActionExecuting method you need to store the cache key (the unique identifier for the output cache) inside the current requests collection. For example:
filterContext.HttpContext.Items["OutputCacheKey"] = cacheKey;
Now modify the ViewPageExtensions class by adding the following (replacing CustomOutputCacheAttribute with the name of your attribute):
var outputCacheKey = webPage.Context.Items["OutputCacheKey"] as string;
if (outputCacheKey != null)
CustomOutputCacheAttribute.ScriptBlocks.AddOrUpdate(outputCacheKey, new StringBuilder(template(null).ToHtmlString()), (k, sb) => {
sb.Append(template(null).ToHtmlString());
return sb;
});
before:
return new MvcHtmlString(string.Empty);
Note: For a slight performance boost you'll also want to make sure you only call "template(null).ToHtmlString()" once.
Now return to your custom OutputCache attribute and add the following only when you are retrieving from the cache inside the OnActionExecuting method:
if (ScriptBlocks.ContainsKey(cacheKey)) {
var scriptBuilder = filterContext.HttpContext.Items["ScriptBlockBuilder"] as StringBuilder ?? new StringBuilder();
scriptBuilder.Append(ScriptBlocks[cacheKey].ToString());
filterContext.HttpContext.Items["ScriptBlockBuilder"] = scriptBuilder;
}
Here's the final code of my attribute:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.UI;
using DevTrends.MvcDonutCaching;
public class CustomOutputCacheAttribute : ActionFilterAttribute, IExceptionFilter {
private readonly IKeyGenerator _keyGenerator;
private readonly IDonutHoleFiller _donutHoleFiller;
private readonly IExtendedOutputCacheManager _outputCacheManager;
private readonly ICacheSettingsManager _cacheSettingsManager;
private readonly ICacheHeadersHelper _cacheHeadersHelper;
private bool? _noStore;
private CacheSettings _cacheSettings;
public int Duration { get; set; }
public string VaryByParam { get; set; }
public string VaryByCustom { get; set; }
public string CacheProfile { get; set; }
public OutputCacheLocation Location { get; set; }
public bool NoStore {
get { return _noStore ?? false; }
set { _noStore = value; }
}
public static ConcurrentDictionary<string, StringBuilder> ScriptBlocks = new ConcurrentDictionary<string, StringBuilder>();
public DonutOutputCacheAttribute() {
var keyBuilder = new KeyBuilder();
_keyGenerator = new KeyGenerator(keyBuilder);
_donutHoleFiller = new DonutHoleFiller(new EncryptingActionSettingsSerialiser(new ActionSettingsSerialiser(), new Encryptor()));
_outputCacheManager = new OutputCacheManager(OutputCache.Instance, keyBuilder);
_cacheSettingsManager = new CacheSettingsManager();
_cacheHeadersHelper = new CacheHeadersHelper();
Duration = -1;
Location = (OutputCacheLocation)(-1);
}
public override void OnActionExecuting(ActionExecutingContext filterContext) {
_cacheSettings = BuildCacheSettings();
var cacheKey = _keyGenerator.GenerateKey(filterContext, _cacheSettings);
if (_cacheSettings.IsServerCachingEnabled) {
var cachedItem = _outputCacheManager.GetItem(cacheKey);
if (cachedItem != null) {
filterContext.Result = new ContentResult {
Content = _donutHoleFiller.ReplaceDonutHoleContent(cachedItem.Content, filterContext),
ContentType = cachedItem.ContentType
};
if (ScriptBlocks.ContainsKey(cacheKey)) {
var scriptBuilder = filterContext.HttpContext.Items["ScriptBlockBuilder"] as StringBuilder ?? new StringBuilder();
scriptBuilder.Append(ScriptBlocks[cacheKey].ToString());
filterContext.HttpContext.Items["ScriptBlockBuilder"] = scriptBuilder;
}
}
}
if (filterContext.Result == null) {
filterContext.HttpContext.Items["OutputCacheKey"] = cacheKey;
var cachingWriter = new StringWriter(CultureInfo.InvariantCulture);
var originalWriter = filterContext.HttpContext.Response.Output;
filterContext.HttpContext.Response.Output = cachingWriter;
filterContext.HttpContext.Items[cacheKey] = new Action<bool>(hasErrors => {
filterContext.HttpContext.Items.Remove(cacheKey);
filterContext.HttpContext.Response.Output = originalWriter;
if (!hasErrors) {
var cacheItem = new CacheItem {
Content = cachingWriter.ToString(),
ContentType = filterContext.HttpContext.Response.ContentType
};
filterContext.HttpContext.Response.Write(_donutHoleFiller.RemoveDonutHoleWrappers(cacheItem.Content, filterContext));
if (_cacheSettings.IsServerCachingEnabled && filterContext.HttpContext.Response.StatusCode == 200)
_outputCacheManager.AddItem(cacheKey, cacheItem, DateTime.UtcNow.AddSeconds(_cacheSettings.Duration));
}
});
}
}
public override void OnResultExecuted(ResultExecutedContext filterContext) {
ExecuteCallback(filterContext, false);
if (!filterContext.IsChildAction)
_cacheHeadersHelper.SetCacheHeaders(filterContext.HttpContext.Response, _cacheSettings);
}
public void OnException(ExceptionContext filterContext) {
if (_cacheSettings != null)
ExecuteCallback(filterContext, true);
}
private void ExecuteCallback(ControllerContext context, bool hasErrors) {
var cacheKey = _keyGenerator.GenerateKey(context, _cacheSettings);
var callback = context.HttpContext.Items[cacheKey] as Action<bool>;
if (callback != null)
callback.Invoke(hasErrors);
}
private CacheSettings BuildCacheSettings() {
CacheSettings cacheSettings;
if (string.IsNullOrEmpty(CacheProfile)) {
cacheSettings = new CacheSettings {
IsCachingEnabled = _cacheSettingsManager.IsCachingEnabledGlobally,
Duration = Duration,
VaryByCustom = VaryByCustom,
VaryByParam = VaryByParam,
Location = (int)Location == -1 ? OutputCacheLocation.Server : Location,
NoStore = NoStore
};
} else {
var cacheProfile = _cacheSettingsManager.RetrieveOutputCacheProfile(CacheProfile);
cacheSettings = new CacheSettings {
IsCachingEnabled = _cacheSettingsManager.IsCachingEnabledGlobally && cacheProfile.Enabled,
Duration = Duration == -1 ? cacheProfile.Duration : Duration,
VaryByCustom = VaryByCustom ?? cacheProfile.VaryByCustom,
VaryByParam = VaryByParam ?? cacheProfile.VaryByParam,
Location = (int)Location == -1 ? ((int)cacheProfile.Location == -1 ? OutputCacheLocation.Server : cacheProfile.Location) : Location,
NoStore = _noStore.HasValue ? _noStore.Value : cacheProfile.NoStore
};
}
if (cacheSettings.Duration == -1)
throw new HttpException("The directive or the configuration settings profile must specify the 'duration' attribute.");
if (cacheSettings.Duration < 0)
throw new HttpException("The 'duration' attribute must have a value that is greater than or equal to zero.");
return cacheSettings;
}
}
I also had to modify the Donut Output Cache library to make IExtendedOutputCacheManager and the OutputCacheManager constructor public.
Please note this has been extracted from my application and may require some minor tweaks. You should also place WriteScriptBlocks at the bottom of the page so it is not called until after all child actions are triggered.
Hope this helps.
Hi so I'm pretty new to MVC3 and Razor and I've been trying to get my head around it the past few days. I've been given a task by my project architect to create a helper method that sorts a drop down list in an MVC View. I have a View that retrieves various data from a Controller and I'm returning some values that I want to appear in a drop down list. I've been told not to sort it in the Controller and also to pass the field that we want to sort by into the helper method. I could do it like below but the architect wants to keep the view free of c sharp code:
#Html.DropDownListFor(model => model.StudyName, new SelectList(ViewBag.StudyTypes, "Value", "Text").OrderBy(l => l.Text))
So I've created some sample code and some extension methods to try and get it to work. My idea is to replicate the existing Html.DropDownList method and allow the passing of 'object htmlAttributes' so I can set the style as part of the method call.
Here's my code so far. I'm returning the data for the drop down in ViewBag.StudyTypes in the Edit Controller method:
public ActionResult Edit(int id)
{
IEnumerable<SelectListItem> mySelectList = new List<SelectListItem>();
IList<SelectListItem> myList = new List<SelectListItem>();
for (int i = 0; i < 5; i++)
{
myList.Add(new SelectListItem()
{ Value = i.ToString(), Text = "My Item " + i.ToString(), Selected = i == 2 }
);
}
mySelectList = myList;
ViewBag.StudyTypes = mySelectList;
StudyDefinition studydefinition = db.StudyDefinitions.Find(id);
return View(studydefinition);
}
Here's my View code:
#model MyStudyWeb.Models.StudyDefinition
#using MyStudyWeb.Helpers
#{
ViewBag.Mode = "Edit";
}
<div>
#Html.DropDownListSorted(new SelectList(ViewBag.StudyTypes, "Value", "Text"))<br />
#Html.DropDownListSorted("MyList", new SelectList(ViewBag.StudyTypes, "Value", "Text"))<br />
</div>
Finally below are the extension methods I'm trying to get to work. The first extension method does nothing, I just get a blank space at that point in the View. The second method kind of works but it's ugly. For the 3rd method I don't know how to specify an 'order by' parameter as the OrderBy on an IEnumerable expects a Linq expression.
namespace StudyDefinition.Helpers
{
public static class HtmlHelperExtensions
{
// 1st sort method: sort the passed in list and return a new sorted list
public static SelectList DropDownListSorted(this HtmlHelper helper, IEnumerable<SelectListItem> selectList)
{
var x = new SelectList(selectList.ToList()).OrderBy(l => l.Text);
return x as SelectList;
}
// 2nd sort method: return IHtml string and create <select> list manually
public static IHtmlString DropDownListSorted(this HtmlHelper helper, string name, SelectList selectList)
{
StringBuilder output = new StringBuilder();
(selectList).OrderBy(l => l.Text);
output.Append("<select id=" + name + " name=" + name + ">");
foreach (var item in selectList)
{
output.Append("<option value=" + item.Value.ToString() + ">" + item.Text + "</option>");
}
output.Append("</select>");
return MvcHtmlString.Create(output.ToString());
}
// 3rd sort method: pass in order by parameter - how do I use this?
public static IHtmlString DropDownListSorted(this HtmlHelper helper, string name, SelectList selectList, string orderBy)
{
StringBuilder output = new StringBuilder();
//How do I use the orderBy parameter?
(selectList).OrderBy(l => l.Text);
output.Append("<select id=" + name + " name=" + name + ">");
foreach (var item in selectList)
{
output.Append("<option value=" + item.Value.ToString() + ">" + item.Text + "</option>");
}
output.Append("</select>");
return MvcHtmlString.Create(output.ToString());
}
}
}
I really don't know the best approach to take, there may be a much simpler way that I'm totally missing and I might be at the point where I can't see the wood for the trees anymore. Some questions
Should I return a SelectList or an MvcHtmlString, or something else entirely?
For the first extension method how do I get the returned SelectList to render in the View?
How to I pass in a parameter to my extension methods that specifies the sort order?
How do I pass an 'object htmlAttributes' parameter, and how do I apply this object / parameter to the SelectList?
If anyone has some ideas or suggestions then I'd appreciate some feedback :)
The first and most important part of your code would be to get rid of any ViewBag/ViewData (which I personally consider as cancer for MVC applications) and use view models and strongly typed views.
So let's start by defining a view model which would represent the data our view will be working with (a dropdownlistg in this example):
public class MyViewModel
{
public string SelectedItem { get; set; }
public IEnumerable<SelectListItem> Items { get; set; }
}
then we could have a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel
{
// I am explicitly putting some items out of order
Items = new[]
{
new SelectListItem { Value = "5", Text = "Item 5" },
new SelectListItem { Value = "1", Text = "Item 1" },
new SelectListItem { Value = "3", Text = "Item 3" },
new SelectListItem { Value = "4", Text = "Item 4" },
}
};
return View(model);
}
}
and a view:
#model MyViewModel
#Html.DropDownListForSorted(
x => x.SelectedItem,
Model.Items,
new { #class = "foo" }
)
and finally the last piece is the helper method which will sort the dropdown by value (you could adapt it to sort by text):
public static class HtmlExtensions
{
public static IHtmlString DropDownListForSorted<TModel, TProperty>(
this HtmlHelper<TModel> helper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> items,
object htmlAttributes
)
{
var model = helper.ViewData.Model;
var orderedItems = items.OrderBy(x => x.Value);
return helper.DropDownListFor(
expression,
new SelectList(orderedItems, "Value", "Text"),
htmlAttributes
);
}
}
Just add in the sorting before you return the items to the dropdown list.
Do this:
Models: StudyViewModel.cs
public class StudyViewModel {
public string StudyName { get; set; }
public string StudyTypes { get; set; }
}
Controller: StudyController.cs
using System.Web.Mvc;
public class StudyController
{
public List<SelectListItem> studyTypes()
{
List<SelectListItem> itemList = new List<SelectListItem>();
for (var i=0; i<5; i++)
{
itemList.Add = new SelectListItem({
Value = i.ToString();
Text = "My Item";
});
}
// You can sort here....
List<SelectListItem> sortedList = itemList.OrderBy(x=>x.Text);
return sortedList;
}
public ActionResult Edit(int id)
{
//You won't need this because you get it using your
//controller's routine, instead
//ViewBag.StudyTypes = studySlots.OrderBy(e => e.Value);
//-- unless you need to add these values to the model for
// some reason (outside of filling the ddl), in which case....
// StudyViewModel svm = new StudyViewModel();
// svm.StudyTypes = studySlots.OrderBy(e => e.Value);
// svm.StudyName = "My Item";
// return View(svm);
// Otherwise, just....
return View();
}
}
View: Edit.cshtml
#Html.DropDownListFor(model => model.StudyName)
.OptionLabel('Select...')
.DataTextField('Text')
.DataValueField('Value')
.Datasource(source =>
{
// This is where you populate your data from the controller
source.Read(read =>
{
read.Action("studyTypes", "Study");
});
})
.Value(Model.StudyName != null ? Model.StudyName.ToString() : "")
)
This way will avoid ViewBags and just use a function to fill in the values, directly.
If you are using a database you can use a query to define the sort element
using (BDMMContext dataContext = new BDMMContext())
{
foreach (Arquiteto arq in dataContext.Arquitetos.SqlQuery("SELECT * FROM Arquitetos ORDER BY Nome"))
{
SelectListItem selectItem = new SelectListItem { Text = arq.Nome, Value = arq.Arquiteto_Id.ToString() };
//
list.Add(selectItem);
}
}
i have seen other posts with answers to this but i cannot get any of them to work for me.. so. this is in my controller->
ViewData["TARGET_DATE"] = new SelectList((from n in _db.ACTION_PLANs select n).ToList(), "TARGET_DATE", "TARGET_DATE");
i want to be able to apply a format on the dates that come back from the db, my DDL looks like this
<td><%=Html.DropDownList("TARGET_DATE", "All")%></td>
does anyone know if there is a way to loop through and format each date, or apply a format to them all. or what would be the best way to do this, if u need more code i can provide, what i really want is to display the date without the time with it. thanks in advance.
I know this post is old, but I ran across it while searching for an answer to the same problem. I ended up writing a FormattableSelectList class that takes a format string as a parameter, and then formats that output of the select list. Here's a sample of how to use it.
#Html.DropDownFor(
o => o.SomeProperty,
new FormattableSelectList(ViewBag.MyItems, "ID", "Time", "{0:f}"),
"Choose a Time"
)
I wrote a short post about it at http://www.jpolete.me/2011/11/16/a-formattable-selectlist-for-net-mvc/. Here's the code for it.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Collections;
using System.Web.UI;
using System.Globalization;
public class FormattableSelectList : SelectList
{
private string _FormatString;
public FormattableSelectList(IEnumerable items, string dataValueField, string dataTextField, string formatString)
: base(items, dataValueField, dataTextField)
{
_FormatString = formatString;
}
public FormattableSelectList(IEnumerable items, string dataValueField, string dataTextField, string formatString, object selectedValue)
: base(items, dataValueField, dataTextField, selectedValue)
{
_FormatString = formatString;
}
public override IEnumerator<SelectListItem> GetEnumerator()
{
return ((!String.IsNullOrEmpty(DataValueField)) ?
GetListItemsWithValueField() :
GetListItemsWithoutValueField()).GetEnumerator();
}
private IList<SelectListItem> GetListItemsWithValueField()
{
HashSet<string> selectedValues = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (SelectedValues != null)
{
selectedValues.UnionWith(from object value in SelectedValues select Convert.ToString(value, CultureInfo.CurrentCulture));
}
var listItems = from object item in Items
let value = Eval(item, DataValueField, null)
select new SelectListItem
{
Value = value,
Text = Eval(item, DataTextField, _FormatString),
Selected = selectedValues.Contains(value)
};
return listItems.ToList();
}
private IList<SelectListItem> GetListItemsWithoutValueField()
{
HashSet<object> selectedValues = new HashSet<object>();
if (SelectedValues != null)
{
selectedValues.UnionWith(SelectedValues.Cast<object>());
}
var listItems = from object item in Items
select new SelectListItem
{
Text = Eval(item, DataTextField, _FormatString),
Selected = selectedValues.Contains(item)
};
return listItems.ToList();
}
private static string Eval(object container, string expression, string formatString)
{
object value = container;
if (!String.IsNullOrEmpty(expression))
{
value = DataBinder.Eval(container, expression);
}
string stringValue;
if (formatString == null)
{
stringValue = Convert.ToString(value, CultureInfo.CurrentCulture);
}
else
{
stringValue = String.Format(formatString, value);
}
return stringValue;
}
}
Here is how I implemented the class above. My client was looking for two dropdownlists containing Start time and End time. In MVC4
Model:
[Display(Name = "Time (From):")]
public int FromTimeId { get; set; }
[Display(Name = "Time (To):")]
public int ToTimeId { get; set; }
public virtual FromTime FromTime { get; set; }
public virtual ToTime ToTime { get; set; }
Controller:
public ActionResult Create()
{
ViewBag.FromTimes = db.FromTimes;
ViewBag.ToTimes = db.ToTimes;
}
View:
<div class="editor-field">
#Html.DropDownListFor(model => model.FromTimeId, new Namespace.FormattableSelectList(ViewBag.FromTimes, "FromTimeId", "FromTimeName", "{0:h:mm tt}"))<br />
#Html.ValidationMessageFor(model => model.FromTimeId)
</div>
</td>
<td>
<div class="editor-label">
#Html.LabelFor(model => model.ToTimeId, "To Time:")
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.ToTimeId, new Namespace.FormattableSelectList(ViewBag.ToTimes, "ToTimeId", "ToTimeName", "{0:h:mm tt}"))<br />
#Html.ValidationMessageFor(model => model.ToTimeId)
</div>