MVC6 TagHelpers with disposable - asp.net-core-mvc

In the older MVC HTML Helpers, one could use IDisposable to wrap content - for example the BeginForm helper would automatically wrap *stuff* with a closing form tag
<% using (Html.BeginForm()) {%>
*stuff*
<% } %>
Is this wrapping of content supported with MVC6 TagHelpers?
For example I would like this
<widget-box title="My Title">Yay for content!</widget-box>
to be expanded into a bootstrap widget-box with wrapping divs:
<div class="widget-box">
<div class="widget-header">
<h4 class="widget-title">My Title</h4>
</div>
<div class="widget-body">
<div class="widget-main">
Yay for content!
</div>
</div>
</div>
Is this possible with TagHelpers?
Solution: I have baked #DanielJG's answer into a working demo on github which consumes WidgetBoxTagHelper.cs (will stay current with Beta/RC/RTM as am using the lib in my production app)

Tag helpers have to implement the interface ITagHelper (as pointed by #NTaylorMullen, the TagHelper class is just a convenience class you can use when implementing it) which forces you to use the methods Process and ProcessAsync, so you cannot rely on adding contents in a Dispose method.
However you have full control over the output content so you can replace/modify it as you need. For example, a quick approximation to your widget tag helper (Using the 1.0 version of the framework):
[HtmlTargetElement("widget-box")]
public class WidgetTagHelper : TagHelper
{
public string Title { get; set; }
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var outerTag = new TagBuilder("div");
outerTag.Attributes.Add("class", output.TagName);
output.MergeAttributes(outerTag);
output.TagName = outerTag.TagName;
//Create the header
var header = new TagBuilder("div");
header.Attributes.Add("class", "widget-header");
header.InnerHtml.Append(this.Title);
output.PreContent.SetHtmlContent(header);
//Create the body and replace original tag helper content
var body = new TagBuilder("div");
body.Attributes.Add("class", "widget-body");
var originalContents = await output.GetChildContentAsync();
body.InnerHtml.Append(originalContents.GetContent());
output.Content.SetHtmlContent(body);
}
}
In your razor you will have:
<widget-box title="My Title">Yay for content!</widget-box>
Which will be rendered as:
<div class="widget-box">
<div class="widget-header">My Title</div>
<div class="widget-body">Yay for content!</div>
</div>
DonĀ“t forget to register the tag helpers in your assembly by adding a #addTagHelper directive to the _ViewImports.cshtml file. For example this will register all helpers in my application:
#addTagHelper *, WebApplication2
OLD beta7 code
In beta7 you had to use the [TargetElement] attribute.
The TagBuilder class had a SetInnerText method you could use to set its context as text.
The code looked like:
[TargetElement("widget-box")]
public class WidgetTagHelper : TagHelper
{
private IHtmlEncoder encoder;
public WidgetTagHelper(IHtmlEncoder encoder)
{
this.encoder = encoder;
}
public string Title { get; set; }
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var outerTag = new TagBuilder("div");
outerTag.Attributes.Add("class", output.TagName);
output.MergeAttributes(outerTag);
output.TagName = outerTag.TagName;
//Create the header
var header = new TagBuilder("div");
header.Attributes.Add("class", "widget-header");
header.SetInnerText(this.Title);
output.PreContent.SetContent(header);
//Create the body and replace original tag helper content
var body = new TagBuilder("div");
body.Attributes.Add("class", "widget-body");
var originalContents = await context.GetChildContentAsync();
using (var writer = new StringWriter())
{
body.TagRenderMode = TagRenderMode.StartTag;
body.WriteTo(writer, encoder);
originalContents.WriteTo(writer, encoder);
body.TagRenderMode = TagRenderMode.EndTag;
body.WriteTo(writer, encoder);
output.Content.SetContent(writer.ToString());
}
}
}
OLD beta5 code
There was an InnerHtml property in the tag helpers.
There was a ToHtmlString method in the tag helpers used to render them as html.
The code looked like:
[TargetElement("widget-box")]
public class WidgetTagHelper: TagHelper
{
public string Title { get; set; }
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var outerTag = new TagBuilder("div");
outerTag.Attributes.Add("class", output.TagName);
output.MergeAttributes(outerTag);
output.TagName = outerTag.TagName;
//Create the header
var header = new TagBuilder("div");
header.Attributes.Add("class", "widget-header");
header.InnerHtml = this.Title;
output.PreContent.SetContent(header.ToHtmlString(TagRenderMode.Normal).ToString());
//Create the body and replace original tag helper content
var body = new TagBuilder("div");
body.Attributes.Add("class", "widget-body");
var originalContents = await context.GetChildContentAsync();
body.InnerHtml = originalContents.GetContent();
output.Content.SetContent(body.ToHtmlString(TagRenderMode.Normal).ToString());
}
}

Related

Kendo UI ListView Template in MVC4

I am trying to get image files from the database and bind it to a KendoUI ListView. The problem is that it is not showing images at all.
This is what I have done:
View
<script type="text/x-kendo-tmpl" id="template">
<div class="product">
<img src="#Url.Content("#:PhotoID# + #:MIMEType#")" />
</div>
</script>
<div id="imageListView2" class="demo-section">
#(Html.Kendo().ListView<WorcesterMarble.ViewModels.PhotosViewModel>()
.Name("listView")
.TagName("div")
.ClientTemplateId("template")
.DataSource(dataSource =>
{
dataSource.Read(read => read.Action("GetImages", "StockReceiptsGrid").Data("passStockIDToListView"));
dataSource.PageSize(1);
})
.Pageable()
.Selectable(selectable => selectable.Mode(ListViewSelectionMode.Multiple))
//.Events(events => events.Change("onChange").DataBound("onDataBound"))
)
</div>
Controller
public JsonResult GetImages([DataSourceRequest] DataSourceRequest request, int stockReceiptID)
{
var photos = _stockPhotosRepository.GetStocReceiptkPhotos(stockReceiptID).ToList();
var photosList = new List<PhotosViewModel>();
//var photosList = new List<FileContentResult>();
if (photos.Count != 0)
{
foreach (var stockPhoto in photos)
{
var photoVm = new PhotosViewModel();
photoVm.PhotoID = stockPhoto.PhotoID;
photoVm.Image = stockPhoto.ImageData;
photoVm.MIMEType = stockPhoto.MIMEType;
// FileContentResult file = File(stockPhoto.ImageData, stockPhoto.MIMEType);
photosList.Add(photoVm);
}
return Json(photosList.ToList(), JsonRequestBehavior.AllowGet);
}
else
{
return null;
//FilePathResult file = this.File("/Content/Images/80.jpeg", "image/jpeg");
//return file;
}
return null;
}
Photo View Model:
public class PhotosViewModel
{
public int PhotoID { get; set; }
public byte[] Image { get; set; }
public string MIMEType { get; set; }
public int StockReceiptID { get; set; }
}
I am not sure if the problem is caused by the image url setting in the template. as you see it is not actually a url because the image is not saved anywhere except from the database. this is a screenshot of how the listview looks like; simply blank even though there must 15 images displayed!
Please let me know any clues or solutions to this problem.
I know this is a bit older, but what you need to do is change the line return Json(photosList.ToList(), JsonRequestBehavior.AllowGet); to the following:
return Json(photosList.ToDataSourceResult(request),
JsonRequestBehavior.AllowGet);
If the method ToDataSourceResult is not recognized, you have to add
using Kendo.Mvc.Extensions;
on top of your document.
It looks like you're missing a return in your controller (just before the end of your if)
return Json(photosList.ToList(), JsonRequestBehavior.AllowGet);
EDIT
Also, I noticed this:
<img src="#Url.Content("#:PhotoID# + #:MIMEType#")" />
Shouldn't that be:
<img src="#Url.Content("#:ImageData#")" />
or something similar?
It might be to late to answer, but your issue is that the json data being sent back to your view is to large so your images are not showing, rather save your images to a file and then render your images via a URL.

How to dynamically generate CSS file from database in ASP.NET MVC

i was going through a post in stackoverflow and the link is Dynamically generate CSS file from database in ASP.NET MVC
here i am giving the full code and i like to know few thinks from the code.
I think the simplest way would be to add something like the following action method to a controller:
public class CssController : Controller
{
public ContentResult GetTheme()
{
var builder = new StringBuilder();
IDictionary<string, IDictionary<string, string>> css = new Dictionary<string, IDictionary<string, string>>();
/* Populate css object from the database */
foreach (var selector in css)
{
builder.Append(selector.Key);
builder.Append(" { ");
foreach (var entry in selector.Value)
{
builder.Append(string.Format("{0}: {1}; ", entry.Key, entry.Value));
}
builder.AppendLine("}");
}
return Content(builder.ToString(), "text/css");
}
}
Now in your page you can reference it like so:
<link href="<%: Url.RouteUrl(new { controller= "CssController", action = "GetCss" }) %>" rel="stylesheet" type="text/css" />
my question is what file name will show in href ?
the controller CssController & action method GetCss is writing string to out going stream so no file name should show in href....how this code will work?? please guide me to understand this catch......thanks

Updating only the partial view contained in a mvc 3 page?

I have a MVC 3 page that returns a list of user responses with a partial view called "memo" (which displays/add memos) for each response. When I add a memo to a response, it should update the db and the list of memos for that response. It should be partial page update via ajax, which effects only the partial view "memo".
The view Response.chtml that contains "memo":
#using (Html.BeginForm("Response", "User", FormMethod.Post, new { id = "UserResponse" }))
{
.... code removed ....
#foreach (var response in Model)
{
<div class="qna"><input type="text" id=#response.responseId value="#response.ResponseText" />
<div>#Html.Partial("_memo", response.responseId)</div>
}
.....
The partial page "_memo.chtml":
<div>add memo</div>
<ul id="memos">
#foreach (var memo in Model) {
<li>#memo.Text</li>
}
</ul>
<form method="post" id="memoForm"
action="#Url.Action("AddMemo")">
#Html.TextArea("Memo", new { rows = 5, cols = 50 })
<br />
<input type="submit" value="Add" />
</form>
Controller for view User/Response:
[HttpGet]
public ActionResult Response(id)
{
.....
return View(responses);
I just started with the code above, need help filling the blanks.
If I pass the response Id to the partial view, how do I pull the list of memos for that response? Will it involve ajax? (instead of ..Partial("_memo", response.memos))
How do I update the partial view via ajax call. What is ajax call (sample code) on the client side and how would the controller look? When the ajax call is successful, how do I update the list memos div="memos" to reflect the new memo?
Will the form action from Response conflict with form action of the partial view Memo?
Answers to Questions:
You shouldn't pass the responseId to the partial, you should pass the memo collection from your response object and make your partial view strongly typed to that collection.
See full code example below.
You don't need the form in the partial since you're making a simple ajax call to add the new memo. See full code example below.
This is a modified example from a project I am currently working on:
There is a bit of code to follow, so here goes:
This is my model. There are several sections on a career planning form, one of which is a section to select and update competencies. The SelectCompetencies model has a collection of competencies within it. The user will have the ability to add competencies. When they do, it will be added to the database and will update the list of competencies in the partial.
public class CareerPlanningFormViewModel
{
// code removed ...
public SelectCompetenciesModel SelectCompetencies { get; set; }
// code removed ...
}
public class SelectCompetenciesModel
{
public int CareerPlanningFormID { get; set; }
public IList<CompetencyModel> Competencies { get; set; }
public byte MaximumCompetenciesAllowed { get; set; }
}
public class CompetencyModel
{
public int CompetencyID { get; set; }
public int? CompetencyOptionID { get; set; }
public string ActionPlan { get; set; }
public IDictionary<int, string> CompetencyOptions { get; set; }
}
The main view of the career planning form: /Views/CPF/CareerPlanningForm.cshtml
#model MyNamespace.Models.CareerPlanningForm.CareerPlanningFormViewModel
<link rel="stylesheet" href="#Url.Content("~/Content/CreateCPF.css")" />
#using (Html.BeginForm())
{
// other sections loaded here...
// code removed for brevity...
#Html.Partial("SelectCompetencies", Model.SelectCompetencies)
// other sections loaded here...
// code removed for brevity...
}
The SelectCompetencies partial: /Views/CPF/SelectCompetencies.cshtml
The user will fill in the new action plan text and click the add competency button.
That will post via ajax to CPFController/NewCompetencyTemplate
#model MyNamespace.Models.CareerPlanningForm.SelectCompetenciesModel
#Html.HiddenFor(m => m.CareerPlanningFormID)
<h3>Select Competencies</h3>
<p class="guidance">
Select up to #Model.MaximumCompetenciesAllowed competencies to focus on improving.
</p>
<table id="CompetenciesTable">
<thead>
<tr>
<th>Competency</th>
<th>Action Plan:</th>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.Competencies.Count(); i++)
{
#Html.EditorFor(m => m.Competencies[i])
}
</tbody>
<tfoot id="CompetenciesTableFooter" class="#(Model.Competencies.Count() < Model.MaximumCompetenciesAllowed ? "" : "hidden")">
<tr>
<td colspan="2">
#Html.TextArea("NewActionPlanText")
#Html.Button(ButtonType.Button, "Add Another Competency", "add", new { id = "AddCompetencyButton" })
</td>
</tr>
</tfoot>
</table>
#section script
{
<script>
jQuery(document).ready(function ($) {
var competenciesTableBody = $('#CompetenciesTable tbody'),
competenciesTableFooter = $('#CompetenciesTableFooter'),
addCompetencyButton = $('#AddCompetencyButton'),
newCompetencyTemplateUrl = '#Url.Content("~/CPF/NewCompetencyTemplate")',
count = competenciesTableBody.find('tr').length,
newActionPlanText = $('#NewActionPlanText'),
careerPlanningFormID = $('#CareerPlanningFormID');
addCompetencyButton.click(function () {
$.ajax({
url: newCompetencyTemplateUrl(),
type: 'POST',
data: {
careerPlanningFormID: careerPlanningFormID,
actionPlan: newActionPlanText,
itemCount: count
},
dataType: 'html',
success: function (data) {
var elements = $(data);
// other code removed here...
competenciesTableBody.append(elements);
// other code removed here...
}
});
});
});
</script>
}
Views/CPF/EditorTemplates/CompetencyModel.cshtml
#model MyNamespace.Models.CareerPlanningForm.CompetencyModel
<tr class="competency">
<td>
#Html.DropDownListFor(m => m.CompetencyOptionID, new SelectList(Model.CompetencyOptions, "Key", "Value"), "Select competency...")
</td>
<td>
#Html.TextAreaFor(m => m.ActionPlan, new { #class = "competencyActionPlan" })
#Html.HiddenFor(m => m.CompetencyID)
</td>
</tr>
The controller containing the action to add the new competency: /Controllers/CPFController.cs
This will call the CareerPlanningFormService to add the new competency and will return a partial view for NewCompetencyTemplate that will render out the new competency
public class CPFController : Controller
{
private readonly ICareerPlanningFormService careerPlanningFormService;
public CPFController(ICareerPlanningFormService careerPlanningFormService)
{
this.careerPlanningFormService = careerPlanningFormService;
}
[HttpPost]
public PartialViewResult NewCompetencyTemplate(int careerPlanningFormID, int itemCount, string newActionPlanText)
{
var count = itemCount + 1;
// Even though we're only rendering a single item template, we use a list
// to trick MVC into generating fields with correctly indexed name attributes
// i.e. Competencies[1].ActionPlan
var model = new SelectCompetenciesModel
{
Competencies = Enumerable.Repeat<CompetencyModel>(null, count).ToList()
};
model.Competencies[count - 1] = this.careerPlanningFormService.BuildNewCompetencyModel(careerPlanningFormID, newActionPlanText);
return this.PartialView(model);
}
}
My service class: CareerPlanningFormService.cs
This handles the business logic and makes the calls to the repository to add the item to the database and returns a new CompetencyModel
public class CareerPlanningFormService : ICareerPlanningFormService
{
private readonly IMyRenamedRepository repository;
private readonly IPrincipal currentUser;
public CareerPlanningFormService(
IMyRenamedRepository repository,
IPrincipal currentUser)
{
this.repository = repository;
this.currentUser = currentUser;
}
public CompetencyModel BuildNewCompetencyModel(int careerPlanningFormID, string newActionPlanText)
{
var competency = new Competency
{
CareerPlanningFormID = careerPlanningFormID,
CompetencyOptionID = null,
ActionPlan = newActionPlanText
};
this.repository.Add(competency);
this.repository.Commit();
return new CompetencyModel
{
CompetencyID = competency.CompetencyID,
CompetencyOptionID = competency.CompetencyOptionID,
ActionPlan = competency.ActionPlan,
CompetencyOptions = this.GetCompetencyOptionsForCareerPlanningFormID(careerPlanningFormID)
};
}
}
Now, the partial for NewCompetencyTemplate: Views/CPF/NewCompetencyTemplate.cshtml
This is very simple, it simply renders the same editor template as above, for the last competency in the collection (which we just added)
#model MyNamespace.Models.CareerPlanningForm.SelectCompetenciesViewModel
#Html.EditorFor(m => m.Competencies[Model.Competencies.Count() - 1])
When the ajax call succeeds, it will receive this partial back from the controller action method it called. It then takes the partial and appends it to the competencies table body
// snippet from ajax call above
competenciesTableBody.append(elements);
I hope this helps. Let me know if you have any additional questions.
While you're correct that you can do it just by returning a partial view containing the updated content, you may also consider using jQuery's load method.
Look here, in particular at the "loading page fragments" section. Basically you can just get the original page again and jQuery will "extract" the content you want as long as it can be targetted by a selector (such as a div id).
Note, this solution is not suitable in all cases as there will be redundant markup in the response from the server because you will be discarding the rest of the page content and just using the updated part.

Displaying an uploaded image in MVC 3 Razor

Well, this newbie is doing something wrong when displaying images uploaded to the server:
model:
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public string ImageUrl { get; set; }
}
controller (upload - called by the [HttpPost] public ActionResult Create):
public void Upload(Person person)
{
var image = WebImage.GetImageFromRequest();
var filename = Path.GetFileName(image.FileName);
var path = Path.Combine(Server.MapPath("~/App_Data/Uploads/Fotos"), filename);
image.Save(path);
person.ImageUrl = Url.Content(Path.Combine("~/App_Data/Uploads/Fotos", filename));
}
create view:
...
#using (Html.BeginForm("Create", "Person", FormMethod.Post, new { #encType = "multipart/form-data" }))
{
...
#FileUpload.GetHtml(initialNumberOfFiles: 1, allowMoreFilesToBeAdded: false, includeFormTag: false, uploadText: "image")
...
<div>
<input type="submit" value="Create" /> |
#Html.ActionLink("Back", "Index")
</div>
}
So far, so good, the image is uploaded to the folder and the url is saved
Now, I want to see it in the Detail View
detail view:
<div class="display-foto">
<img src="#Url.Content(Server.MapPath(Model.ImageUrl))" alt="IMAGE" />
</div>
Viewing the generated code, everything seems to be alright:
<img src="D:\Users\x\Documents\Visual Studio 2010\Projects\CMI_AD\CMI_AD\App_Data\Uploads\Fotos\_nofoto.jpg" alt="IMAGE" />
But the fact is that nothing appears on the screen except the text "IMAGE".
What am I doing wrong?
P.S. I've tried without the Server.MapPath, using the relative address "~\App_Data\Uploads\Fotos_nofoto.jpg" and the result is the same.
--- EDIT ---
#kmcc049: I've tried your suggestion creating a helper
public static class MyHelpers
{
public static IHtmlString MyImage(this HtmlHelper htmlHelper, string url)
{
var urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext);
var img = new TagBuilder("img");
img.Attributes["alt"] = "[IMAGE]";
img.Attributes["src"] = UrlHelper.GenerateContentUrl(url, htmlHelper.ViewContext.HttpContext);
return MvcHtmlString.Create(img.ToString(TagRenderMode.SelfClosing));
}
}
the call in the view:
#Html.MyImage(Model.ImageUrl)
the generated code is
<img alt="[IMAGE]" src="/App_Data/Uploads/Fotos/_nofoto.jpg" />
but the result is the same: no image :(
--- SOLVED ---
Apparently the App_Data is not a good location to save uploaded files because accessing them will result an Error 403 - Forbidden. I move the files to ~/Uploads/Fotos and it works.
That's not a valid HTTP path. That's the path to the folder on your computer.
try the UrlHelper.GenerateContentUrl() method instead
http://msdn.microsoft.com/en-us/library/system.web.mvc.urlhelper.generatecontenturl.aspx

ASP.NET MVC 3 - Data Annoation and Max Length/Size for Textbox Rendering

I know on the Razor View file, we can do something like this
#Html.TextBox("username", null, new { maxlength = 20, autocomplete = "off" })
However, I am hoping to create a model for the MVC that can be used to create a form with explicitly defined the size and max length of the textboxes. I try [StringLength(n)] on top of the properties of the model, but that seems to only do the validation ratherh set the size of the textbox.
Is there anyway that we can define the length of the text field as a data annotation on top of a property of a model?
So ultimately, we could just create the whole form by using razor to map to a model rather than explicitly pick up the model properties one by one in order to set the textbox size.
Here is a outline of a custom helper that uses StringLengthAttribute.
public class MyModel
{
[StringLength(50)]
public string Name{get; set;}
}
public MvcHtmlString MyTextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> helper,
Expression<Func<TModel, TProperty>> expression)
{
var attributes = new Dictionary<string, Object>();
var memberAccessExpression = (MemberExpression)expression.Body;
var stringLengthAttribs = memberAccessExpression.Member.GetCustomAttributes(
typeof(System.ComponentModel.DataAnnotations.StringLengthAttribute), true);
if (stringLengthAttribs.Length > 0)
{
var length = ((StringLengthAttribute)stringLengthAttribs[0]).MaximumLength;
if (length > 0)
{
attributes.Add("size", length);
attributes.Add("maxlength", length);
}
}
return helper.TextBoxFor(expression, attributes);
}
Does this not work?
public class ViewModel
{
[StringLength(20)]
public string UserName {get;set;}
}
In the View:
#Html.TextBoxFor(x => x.UserName, new {autocomplete = "off"})
or:
#Html.EditorFor(x => x.UserName)
I find that I prefer my views to just Call Html.EditorFor(...). This means that the Editor and Display templates decide the fate of controls in my view, such that my view code gets cleaned up a lot - it just has html and generic requests for editors.
The following link gives a working sample of getting this working in an Editor Template
https://jefferytay.wordpress.com/2011/12/20/asp-net-mvc-string-editor-template-which-handles-the-stringlength-attribute/
I'm using similar in my String.cshtml Editor Template (goes in Shared/EditorTemplates ).
#model object
#using System.ComponentModel.DataAnnotations
#{
ModelMetadata meta = ViewData.ModelMetadata;
Type tModel = meta.ContainerType.GetProperty(meta.PropertyName).PropertyType;
}
#if(typeof(string).IsAssignableFrom(tModel)) {
var htmlOptions = new System.Collections.Generic.Dictionary<string, object>();
var stringLengthAttribute = (StringLengthAttributeAdapter)ViewData.ModelMetadata.GetValidators(this.ViewContext.Controller.ControllerContext).Where(v => v is StringLengthAttributeAdapter).FirstOrDefault();
if (stringLengthAttribute != null && stringLengthAttribute.GetClientValidationRules().First().ValidationParameters["max"] != null)
{
int maxLength = (int)stringLengthAttribute.GetClientValidationRules().First().ValidationParameters["max"];
htmlOptions.Add("maxlength", maxLength);
if (maxLength < 20)
{
htmlOptions.Add("size", maxLength);
}
}
htmlOptions.Add("class", "regular-field");
<text>
#Html.TextBoxFor(m => m, htmlOptions)
</text>
}
else if(typeof(Enum).IsAssignableFrom(tModel)) {
//Show a Drop down for an enum using:
//Enum.GetValues(tModel)
//This is beyond this article
}
//Do other things for other types...
Then my model is annotated such as:
[Display(Name = "Some Field", Description = "Description of Some Field")]
[StringLength(maximumLength: 40, ErrorMessage = "{0} max length {1}.")]
public string someField{ get; set; }
And my View simply calls:
<div class="editor-label">
#Html.LabelWithTooltipFor(model => model.something.someField)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.something.someField)
#Html.ValidationMessageFor(model => model.something.someField)
</div>
You might also notice that my String.cshtml Editor Template also auto-magically handles Enum's, but that is starting to digress from the current topic, so I nixed that code, I'll just say here that the String Editor Template can pull extra weight, and likely google has someting on that https://www.google.com/search?q=string+editor+template+enum
Label With Tooltip For is a custom HTML helper that just drops the description into the label title, for more information on mouse over for every label.
I'd recommend this approach if you want to do this in an Editor Template.

Resources