EditorFor() and html properties - c#

Asp.Net MVC 2.0 preview builds provide helpers like
Html.EditorFor(c => c.propertyname)
If the property name is string, the above code renders a texbox.
What if I want to pass in MaxLength and Size properties to the text box or my own css class property?
Do I need to create one template for each size and length combinations in my application? If so, that doesn't make the default templates that usable.

In MVC3, you can set width as follows:
#Html.TextBoxFor(c => c.PropertyName, new { style = "width: 500px;" })

I solved this by creating an EditorTemplate named String.ascx in my /Views/Shared/EditorTemplates folder:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<string>" %>
<% int size = 10;
int maxLength = 100;
if (ViewData["size"] != null)
{
size = (int)ViewData["size"];
}
if (ViewData["maxLength"] != null)
{
maxLength = (int)ViewData["maxLength"];
}
%>
<%= Html.TextBox("", Model, new { Size=size, MaxLength=maxLength }) %>
In my view, I use
<%= Html.EditorFor(model => model.SomeStringToBeEdited, new { size = 15, maxLength = 10 }) %>
Works like a charm for me!

None of the answers in this or any other thread on setting HTML attributes for #Html.EditorFor were much help to me. However, I did find a great answer at
Styling an #Html.EditorFor helper
I used the same approach and it worked beautifully without writing a lot of extra code. Note that the id attribute of the html output of Html.EditorFor is set. The view code
<style type="text/css">
#dob
{
width:6em;
}
</style>
#using (Html.BeginForm())
{
Enter date:
#Html.EditorFor(m => m.DateOfBirth, null, "dob", null)
}
The model property with data annotation and date formatting as "dd MMM yyyy"
[Required(ErrorMessage= "Date of birth is required")]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:dd MMM yyyy}")]
public DateTime DateOfBirth { get; set; }
Worked like a charm without writing a whole lot of extra code. This answer uses ASP.NET MVC 3 Razor C#.

May want to look at Kiran Chand's Blog post, he uses custom metadata on the view model such as:
[HtmlProperties(Size = 5, MaxLength = 10)]
public string Title { get; set; }
This is combined with custom templates that make use of the metadata. A clean and simple approach in my opinion but I would like to see this common use case built-in to mvc.

I'm surprised no one mentioned passing it in "additionalViewData" and reading it on the other side.
View (with line breaks, for clarity):
<%= Html.EditorFor(c => c.propertyname, new
{
htmlAttributes = new
{
#class = "myClass"
}
}
)%>
Editor template:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<string>" %>
<%= Html.TextBox("", Model, ViewData["htmlAttributes"])) %>

The problem is, your template can contain several HTML elements, so MVC won't know to which one to apply your size/class. You'll have to define it yourself.
Make your template derive from your own class called TextBoxViewModel:
public class TextBoxViewModel
{
public string Value { get; set; }
IDictionary<string, object> moreAttributes;
public TextBoxViewModel(string value, IDictionary<string, object> moreAttributes)
{
// set class properties here
}
public string GetAttributesString()
{
return string.Join(" ", moreAttributes.Select(x => x.Key + "='" + x.Value + "'").ToArray()); // don't forget to encode
}
}
In the template you can do this:
<input value="<%= Model.Value %>" <%= Model.GetAttributesString() %> />
In your view you do:
<%= Html.EditorFor(x => x.StringValue) %>
or
<%= Html.EditorFor(x => new TextBoxViewModel(x.StringValue, new IDictionary<string, object> { {'class', 'myclass'}, {'size', 15}}) %>
The first form will render default template for string. The second form will render the custom template.
Alternative syntax use fluent interface:
public class TextBoxViewModel
{
public string Value { get; set; }
IDictionary<string, object> moreAttributes;
public TextBoxViewModel(string value, IDictionary<string, object> moreAttributes)
{
// set class properties here
moreAttributes = new Dictionary<string, object>();
}
public TextBoxViewModel Attr(string name, object value)
{
moreAttributes[name] = value;
return this;
}
}
// and in the view
<%= Html.EditorFor(x => new TextBoxViewModel(x.StringValue).Attr("class", "myclass").Attr("size", 15) %>
Notice that instead of doing this in the view, you may also do this in controller, or much better in the ViewModel:
public ActionResult Action()
{
// now you can Html.EditorFor(x => x.StringValue) and it will pick attributes
return View(new { StringValue = new TextBoxViewModel(x.StringValue).Attr("class", "myclass").Attr("size", 15) });
}
Also notice that you can make base TemplateViewModel class - a common ground for all your view templates - which will contain basic support for attributes/etc.
But in general I think MVC v2 needs a better solution. It's still Beta - go ask for it ;-)

I think using CSS is the way to go. I wish I could do more with .NET coding, like in XAML, but in the browser CSS is king.
Site.css
#account-note-input {
width:1000px;
height:100px;
}
.cshtml
<div class="editor-label">
#Html.LabelFor(model => model.Note)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Note, null, "account-note-input", null)
#Html.ValidationMessageFor(model => model.Note)
</div>
Joe

As at MVC 5, if you wish to add any attributes you can simply do
#Html.EditorFor(m => m.Name, new { htmlAttributes = new { #required = "true", #anotherAttribute = "whatever" } })
Information found from this blog

I don't know why it does not work for Html.EditorFor but I tried TextBoxFor and it worked for me.
#Html.TextBoxFor(m => m.Name, new { Class = "className", Size = "40"})
...and also validation works.

You can define attributes for your properties.
[StringLength(100)]
public string Body { get; set; }
This is known as System.ComponentModel.DataAnnotations.
If you can't find the ValidationAttribute that you need you can allways define custom attributes.
Best Regards,
Carlos

This may not be the slickest solution, but it is straightforward. You can write an extension to the HtmlHelper.EditorFor class. In that extension, you can supply an options parameter that will write the options to the ViewData for the helper. Here's some code:
First, the extension method:
public static MvcHtmlString EditorFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, TemplateOptions options)
{
return helper.EditorFor(expression, options.TemplateName, new
{
cssClass = options.CssClass
});
}
Next, the options object:
public class TemplateOptions
{
public string TemplateName { get; set; }
public string CssClass { get; set; }
// other properties for info you'd like to pass to your templates,
// and by using an options object, you avoid method overload bloat.
}
And finally, here's the line from the String.ascx template:
<%= Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, new { #class = ViewData["cssClass"] ?? "" }) %>
Frankly, I think this is straightforward and clear to the poor soul who has to maintain your code down the road. And it is easy to extend for various other bits of info you'd like to pass to your templates. It's working well so far for me in a project where I'm trying to wrap as much as I can in a set of template to help standardize the surrounding html, a la http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-5-master-page-templates.html.

I wrote a blog entry to answer my own question
Adding html attributes support for Templates - ASP.Net MVC 2.0 Beta

In my practice I found that it is best to use EditorTemplates with only one HtmlHelper in it - TextBox that is in most cases. If I want a template for more complex html structure, I'll write a separate HtmlHelper.
Given that we can stick the whole ViewData object in place of htmlAttributes of the TextBox. In addition we can write some customization code for some of the properties of the ViewData if they need special treatment:
#model DateTime?
#*
1) applies class datepicker to the input;
2) applies additionalViewData object to the attributes of the input
3) applies property "format" to the format of the input date.
*#
#{
if (ViewData["class"] != null) { ViewData["class"] += " datepicker"; }
else { ViewData["class"] = " datepicker"; }
string format = "MM/dd/yyyy";
if (ViewData["format"] != null)
{
format = ViewData["format"].ToString();
ViewData.Remove("format");
}
}
#Html.TextBox("", (Model.HasValue ? Model.Value.ToString(format) : string.Empty), ViewData)
Below are the examples of the syntax in the view and the outputted html:
#Html.EditorFor(m => m.Date)
<input class="datepicker" data-val="true" data-val-required="&#39;Date&#39; must not be empty." id="Date" name="Date" type="text" value="01/08/2012">
#Html.EditorFor(m => m.Date, new { #class = "myClass", #format = "M/dd" })
<input class="myClass datepicker" data-val="true" data-val-required="&#39;Date&#39; must not be empty." id="Date" name="Date" type="text" value="1/08">

Because the question is for EditorFor not TextBoxFor WEFX's suggestion doesn't work.
For changing individual input boxes, you can process the output of the EditorFor method:
<%: new HtmlString(Html.EditorFor(m=>m.propertyname).ToString().Replace("class=\"text-box single-line\"", "class=\"text-box single-line my500pxWideClass\"")) %>
It is also possible to change ALL your EditorFors as it turns out MVC sets the class of EditorFor text boxes with .text-box, therefore you can just override this style, in your stylesheet or on the page.
.text-box {
width: 80em;
}
Additionally, you could set the style for
input[type="text"] {
width: 200px;
}
this overrides .text-box and will change all input text boxes, EditorFor or otherwise.

I also had issue with setting the width of TextBox in MVC3, while setting the Clsss attribute worked for TextArea control but not for TextBoxFor control or EditorFor control:
I tried following & that worked for me:
#Html.TextBoxFor(model => model.Title, new { Class = "textBox", style = "width:90%;" })
also in this case Validations are working perfectly.

One way you could get round it is by having delegates on the view model to handle printing out special rendering like this. I've done this for a paging class, I expose a public property on the model Func<int, string> RenderUrl to deal with it.
So define how the custom bit will be written:
Model.Paging.RenderUrl = (page) => { return string.Concat(#"/foo/", page); };
Output the view for the Paging class:
#Html.DisplayFor(m => m.Paging)
...and for the actual Paging view:
#model Paging
#if (Model.Pages > 1)
{
<ul class="paging">
#for (int page = 1; page <= Model.Pages; page++)
{
<li>#page</li>
}
</ul>
}
It could be seen as over-complicating matters but I use these pagers everywhere and couldn't stand seeing the same boilerplate code to get them rendered.

UPDATE: hm, obviously this won't work because model is passed by value so attributes are not preserved; but I leave this answer as an idea.
Another solution, I think, would be to add your own TextBox/etc helpers, that will check for your own attributes on model.
public class ViewModel
{
[MyAddAttribute("class", "myclass")]
public string StringValue { get; set; }
}
public class MyExtensions
{
public static IDictionary<string, object> GetMyAttributes(object model)
{
// kind of prototype code...
return model.GetType().GetCustomAttributes(typeof(MyAddAttribute)).OfType<MyAddAttribute>().ToDictionary(
x => x.Name, x => x.Value);
}
}
<!-- in the template -->
<%= Html.TextBox("Name", Model, MyExtensions.GetMyAttributes(Model)) %>
This one is easier but not as convinient/flexible.

This is the cleanest and most elegant/simple way to get a solution here.
Brilliant blog post and no messy overkill in writing custom extension/helper methods like a mad professor.
http://geekswithblogs.net/michelotti/archive/2010/02/05/mvc-2-editor-template-with-datetime.aspx

I really liked #tjeerdans answer which utilizes the EditorTemplate named String.ascx in the /Views/Shared/EditorTemplates folder. It seems to be the most straight-forward answer to this question. However, I wanted a template using Razor syntax. In addition, it seems that MVC3 uses the String template as a default (see the StackOverflow question "mvc display template for strings is used for integers") so you need to set the model to object rather than string. My template seems to be working so far:
#model object
#{ int size = 10; int maxLength = 100; }
#if (ViewData["size"] != null) {
Int32.TryParse((string)ViewData["size"], out size);
}
#if (ViewData["maxLength"] != null) {
Int32.TryParse((string)ViewData["maxLength"], out maxLength);
}
#Html.TextBox("", Model, new { Size = size, MaxLength = maxLength})

I solved it!!
For Razor the syntax is:
#Html.TextAreaFor(m=>m.Address, new { style="Width:174px" }) this adjusts the text area width to the width that i defined in the style parameter.
For ASPx the syntax is:
<%=Html.TextAreaFor(m => m.Description, new { cols = "20", rows = "15", style="Width:174px" })%>
this will do the trick

Related

How to extend the HtmlHelper With Ajax effects in ASP.NET MVC 5

This is an ASP.NET MVC 5 Project.
I extended the HtmlHelper. Hers the code:
public static class ValidationMessageExtend
{
public static HtmlString ValidateionUseridTooltip(this HtmlHelper helper, string name)
{
if (
helper.ViewData.ModelState[name] == null ||
helper.ViewData.ModelState[name].Errors == null ||
helper.ViewData.ModelState[name].Errors.Count == 0
)
{
return new HtmlString("");
}
TagBuilder innerTag = new TagBuilder("div");
innerTag.Attributes.Add("class", "tooltip-inner");
innerTag.InnerHtml = helper.ViewData.ModelState[name].Errors[0].ErrorMessage;
TagBuilder tag = new TagBuilder("div");
tag.Attributes.Add("class", "tooltip fade top in");
tag.Attributes.Add("role", "tooltip");
tag.Attributes.Add("id", "tooltip387165");
tag.Attributes.Add("style", "top: 5px; left: 55px; display: block;");
tag.InnerHtml = "<div class='tooltip-arrow' style='left: 50 %;'></div>" + innerTag.ToString(TagRenderMode.Normal);
return new HtmlString(tag.ToString(TagRenderMode.Normal));
}
}
The purpose of this extension is to change the #Html.ValidationMessageFor default style.
I have included jquery.unobtrusive-ajax.js and jquery.validate.js in my view.
I would like to use my own extension #Html.ValidateionUseridTooltip("UserID"). It can not be the same as using #Html.ValidationMessageFor, and has to trigger the verification without refreshing the page.
How can I achieve this?
If you inspect the html that the #Html.ValidationMessageFor() method generates it is, for a valid state (where XX is the property name)
<span class="field-validation-valid" data-valmsg-for="XX" data-valmsg-replace="true"></span>
and for an invalid state
<span class="field-validation-error" data-valmsg-for="XX" data-valmsg-replace="true">
<span for="Name" generated="true" class="">The XX field is required.</span>
</span>
Nowhere in your extension method do you generate the necessary data-* attributes used by the jquery.validate.js and jquery.validate.unobtrusive.js for client side validation. And nor should your. Instead make use of the inbuilt methods and add your additional attributes using the methods overloads.
public static MvcHtmlString ValidateionUseridTooltipFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression)
{
var attributes = new { #class = "tooltip fade top in", role = "tooltip" };
MvcHtmlString validation = ValidationExtensions.ValidationMessageFor(helper,expression, null, attributes);
return validation;
// or if you want to enclose this in the div
TagBuilder div = new TagBuilder("div");
div.Attributes.Add(....);
div.InnerHtml = validation.ToString();
return validation;
}
Note that you have not shown the output your expecting so its not clear which attributes you want applied to which elements so the above code will need to be adjusted to suit your needs.
Note also that the inner <span> element in the html above gets added by the validation plugin files and you have no control over it unless you modify the files.
In addition, you should not be adding an id attribute (duplicate id attributes are invalid html) and adding a series of class names and the adding inline style attributes is unnecessary and poor practice (you should add another class name and create the css for it or modify existing css files)
And finally, if your want to understand how the ValidationMessageFor() method works, you can inspect the source code here.

Umbraco 7 load content media picker in custom section

I've created a custom section in Umbraco 7 that references external urls, but have a requirement to extend it to use exactly the same functionality as the media picker from the 'Content' rich text editor. I don't need any other rich text functionality other than to load the media picker overlay from an icon, and select either an internal or external url.
I've tried to distil the umbraco source code, as well as trying various adaptations of online tutorials, but as yet I can't get the media picker to load.
I know that fundamentally I need:
Another angular controller to return the data from the content
'getall' method
An html section that contains the media picker overlay
A reference in the edit.html in my custom section to launch the overlay.
However, as yet I haven't been able to wire it all together, so any help much appreciated.
So, this is how I came up with the solution.....
The first win was that I discovered 2 excellent tutorial blog posts, upon the shoulders of which this solution stands, so much respect to the following code cats:
Tim Geyssons - Nibble postings:
http://www.nibble.be/?p=440
Markus Johansson - Enkelmedia
http://www.enkelmedia.se/blogg/2013/11/22/creating-custom-sections-in-umbraco-7-part-1.aspx
Create a model object to represent a keyphrase, which will be associated to a new, simple, ORM table.
The ToString() method allows a friendly name to be output on the front-end.
[TableName("Keyphrase")]
public class Keyphrase
{
[PrimaryKeyColumn(AutoIncrement = true)]
public int Id { get; set; }
public string Name { get; set; }
public string Phrase { get; set; }
public string Link { get; set; }
public override string ToString()
{
return Name;
}
}
Create an Umbraco 'application' that will register the new custom section by implementing the IApplication interface. I've called mine 'Utilities' and associated it to the utilities icon.
[Application("Utilities", "Utilities", "icon-utilities", 8)]
public class UtilitiesApplication : IApplication { }
The decorator allows us to supply a name, alias, icon and sort-order of the new custom section.
Create an Umbraco tree web controller that will allow us to create the desired menu behaviour for our keyphrases, and display the keyphrase collection from our database keyphrase table.
[PluginController("Utilities")]
[Umbraco.Web.Trees.Tree("Utilities", "KeyphraseTree", "Keyphrase", iconClosed: "icon-doc", sortOrder: 1)]
public class KeyphraseTreeController : TreeController
{
private KeyphraseApiController _keyphraseApiController;
public KeyphraseTreeController()
{
_keyphraseApiController = new KeyphraseApiController();
}
protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings)
{
var nodes = new TreeNodeCollection();
var keyphrases = _keyphraseApiController.GetAll();
if (id == Constants.System.Root.ToInvariantString())
{
foreach (var keyphrase in keyphrases)
{
var node = CreateTreeNode(
keyphrase.Id.ToString(),
"-1",
queryStrings,
keyphrase.ToString(),
"icon-book-alt",
false);
nodes.Add(node);
}
}
return nodes;
}
protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings)
{
var menu = new MenuItemCollection();
if (id == Constants.System.Root.ToInvariantString())
{
// root actions
menu.Items.Add<CreateChildEntity, ActionNew>(ui.Text("actions", ActionNew.Instance.Alias));
menu.Items.Add<RefreshNode, ActionRefresh>(ui.Text("actions", ActionRefresh.Instance.Alias), true);
return menu;
}
else
{
menu.Items.Add<ActionDelete>(ui.Text("actions", ActionDelete.Instance.Alias));
}
return menu;
}
}
The class decorators and TreeController extension allow us to declare the web controller for our keyphrase tree, associate it to our Utilities custom section, as well as choose an icon and sort order.
We also declare an api controller (we'll get to that!), which will allow us access to our Keyphrase data object.
The GetTreeNodes method allows us to iterate the keyphrase data collection and return the resultant nodes to the view.
The GetMenuNode method allows us to create the menu options we require for our custom section.
We state that if the node is the root (Utilities), then allow us to add child nodes and refresh the node collection.
However, if we are lower in the node tree (Keyphrase) then we only want users to be able to delete the node (ie the user shouldn't be allowed to create another level of nodes deeper than Keyphrase)
Create an api controller for our Keyphrase CRUD requests
public class KeyphraseApiController : UmbracoAuthorizedJsonController
{
public IEnumerable<Keyphrase> GetAll()
{
var query = new Sql().Select("*").From("keyphrase");
return DatabaseContext.Database.Fetch<Keyphrase>(query);
}
public Keyphrase GetById(int id)
{
var query = new Sql().Select("*").From("keyphrase").Where<Keyphrase>(x => x.Id == id);
return DatabaseContext.Database.Fetch<Keyphrase>(query).FirstOrDefault();
}
public Keyphrase PostSave(Keyphrase keyphrase)
{
if (keyphrase.Id > 0)
DatabaseContext.Database.Update(keyphrase);
else
DatabaseContext.Database.Save(keyphrase);
return keyphrase;
}
public int DeleteById(int id)
{
return DatabaseContext.Database.Delete<Keyphrase>(id);
}
}
Create the custom section views with angular controllers, which is the current architectual style in Umbraco 7.
It should be noted that Umbraco expects that your custom section components are put into the following structure App_Plugins//BackOffice/
We need a view to display and edit our keyphrase name, target phrase and url
<form name="keyphraseForm"
ng-controller="Keyphrase.KeyphraseEditController"
ng-show="loaded"
ng-submit="save(keyphrase)"
val-form-manager>
<umb-panel>
<umb-header>
<div class="span7">
<umb-content-name placeholder=""
ng-model="keyphrase.Name" />
</div>
<div class="span5">
<div class="btn-toolbar pull-right umb-btn-toolbar">
<umb-options-menu ng-show="currentNode"
current-node="currentNode"
current-section="{{currentSection}}">
</umb-options-menu>
</div>
</div>
</umb-header>
<div class="umb-panel-body umb-scrollable row-fluid">
<div class="tab-content form-horizontal" style="padding-bottom: 90px">
<div class="umb-pane">
<umb-control-group label="Target keyphrase" description="Keyphrase to be linked'">
<input type="text" class="umb-editor umb-textstring" ng-model="keyphrase.Phrase" required />
</umb-control-group>
<umb-control-group label="Keyphrase link" description="Internal or external url">
<p>{{keyphrase.Link}}</p>
<umb-link-picker ng-model="keyphrase.Link" required/>
</umb-control-group>
<div class="umb-tab-buttons" detect-fold>
<div class="btn-group">
<button type="submit" data-hotkey="ctrl+s" class="btn btn-success">
<localize key="buttons_save">Save</localize>
</button>
</div>
</div>
</div>
</div>
</div>
</umb-panel>
</form>
This utilises umbraco and angular markup to display data input fields dynamically and associate our view to an angular controller that interacts with our data layer
angular.module("umbraco").controller("Keyphrase.KeyphraseEditController",
function ($scope, $routeParams, keyphraseResource, notificationsService, navigationService) {
$scope.loaded = false;
if ($routeParams.id == -1) {
$scope.keyphrase = {};
$scope.loaded = true;
}
else {
//get a keyphrase id -> service
keyphraseResource.getById($routeParams.id).then(function (response) {
$scope.keyphrase = response.data;
$scope.loaded = true;
});
}
$scope.save = function (keyphrase) {
keyphraseResource.save(keyphrase).then(function (response) {
$scope.keyphrase = response.data;
$scope.keyphraseForm.$dirty = false;
navigationService.syncTree({ tree: 'KeyphraseTree', path: [-1, -1], forceReload: true });
notificationsService.success("Success", keyphrase.Name + " has been saved");
});
};
});
Then we need html and corresponding angular controller for the keyphrase delete behaviour
<div class="umb-pane" ng-controller="Keyphrase.KeyphraseDeleteController">
<p>
Are you sure you want to delete {{currentNode.name}} ?
</p>
<div>
<div class="umb-pane btn-toolbar umb-btn-toolbar">
<div class="control-group umb-control-group">
<a href="" class="btn btn-link" ng-click="cancelDelete()"
<localize key="general_cancel">Cancel</localize>
</a>
<a href="" class="btn btn-primary" ng-click="delete(currentNode.id)">
<localize key="general_ok">OK</localize>
</a>
</div>
</div>
</div>
</div>
Utilise Umbraco's linkpicker to allow a user to select an internal or external url.
We need html markup to launch the LinkPicker
<div>
<ul class="unstyled list-icons">
<li>
<i class="icon icon-add blue"></i>
<a href ng-click="openLinkPicker()" prevent-default>Select</a>
</li>
</ul>
</div>
And an associated directive js file that launches the link picker and posts the selected url back to the html view
angular.module("umbraco.directives")
.directive('umbLinkPicker', function (dialogService, entityResource) {
return {
restrict: 'E',
replace: true,
templateUrl: '/App_Plugins/Utilities/umb-link-picker.html',
require: "ngModel",
link: function (scope, element, attr, ctrl) {
ctrl.$render = function () {
var val = parseInt(ctrl.$viewValue);
if (!isNaN(val) && angular.isNumber(val) && val > 0) {
entityResource.getById(val, "Content").then(function (item) {
scope.node = item;
});
}
};
scope.openLinkPicker = function () {
dialogService.linkPicker({ callback: populateLink });
}
scope.removeLink = function () {
scope.node = undefined;
updateModel(0);
}
function populateLink(item) {
scope.node = item;
updateModel(item.url);
}
function updateModel(id) {
ctrl.$setViewValue(id);
}
}
};
});
There is one final js file that allows us to send data across the wire, with everyone's favourite http verbs GET, POST(handles put too here too) and DELETE
angular.module("umbraco.resources")
.factory("keyphraseResource", function ($http) {
return {
getById: function (id) {
return $http.get("BackOffice/Api/KeyphraseApi/GetById?id=" + id);
},
save: function (keyphrase) {
return $http.post("BackOffice/Api/KeyphraseApi/PostSave", angular.toJson(keyphrase));
},
deleteById: function (id) {
return $http.delete("BackOffice/Api/KeyphraseApi/DeleteById?id=" + id);
}
};
});
In addition, we will need a package manifest to register our javascript behaviour
{
javascript: [
'~/App_Plugins/Utilities/BackOffice/KeyphraseTree/edit.controller.js',
'~/App_Plugins/Utilities/BackOffice/KeyphraseTree/delete.controller.js',
'~/App_Plugins/Utilities/keyphrase.resource.js',
'~/App_Plugins/Utilities/umbLinkPicker.directive.js'
]
}
Implement tweaks to allow the CMS portion of the solution to work correctly.
At this point we've almost got our custom section singing, but we just need to jump a couple more Umbraco hoops, namely
a) add a keyphrase event class that creates our keyphrase db table if it doesn't exist (see point 8)
b) fire up Umbraco and associate the new custom section to the target user (from the User menu)
c) alter the placeholder text for the custom section by searching for it in umbraco-->config-->en.xml and swapping out the placeholder text for 'Utilities'
Intercept target content fields of target datatypes when content is saved or published
The requirement I was given was to intercept the body content of a news article, so you'll need to create a document type in Umbraco that has, for example, a title field of type 'Textstring', and bodyContent field of type 'Richtext editor'.
You'll also want a, or many, keyphrase(s) to target, which should now be in a new Umbraco custom section, 'Utilities'
Here I've targeted the keyphrase 'technology news' to link to the bbc technology news site so that any time I write the phrase 'technology news' the href link will be inserted automatically.
This is obviously quite a simple example, but would be quite powerful if a user needed to link to certain repetitive legal documents, for example tax, property, due dilligence, for example, which could be hosted either externally or within the CMS itself. The href link will open an external resource in a new tab, and internal resource in the same window (we'll get to that in Point 9)
So, the principle of what we're trying to achieve is to intercept the Umbraco save event for a document and manipulate our rich text to insert our link. This is done as follows:
a) Establish a method (ContentServiceOnSaving) that will fire when a user clicks 'save', or 'publish and save'.
b) Target our desired content field to find our keyphrases.
c) Parse the target content html against our keyphrase collection to create our internal/external links.
NB: If you just want to get the custom section up and running, you only need the ApplicationStarted method to create the KeyPhrase table.
public class KeyphraseEvents : ApplicationEventHandler
{
private KeyphraseApiController _keyphraseApiController;
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication,
ApplicationContext applicationContext)
{
_keyphraseApiController = new KeyphraseApiController();
ContentService.Saving += ContentServiceOnSaving;
var db = applicationContext.DatabaseContext.Database;
if (!db.TableExist("keyphrase"))
{
db.CreateTable<Keyphrase>(false);
}
}
private void ContentServiceOnSaving(IContentService sender, SaveEventArgs<IContent> saveEventArgs)
{
var keyphrases = _keyphraseApiController.GetAll();
var keyphraseContentParser = new KeyphraseContentParser();
foreach (IContent content in saveEventArgs.SavedEntities)
{
if (content.ContentType.Alias.Equals("NewsArticle"))
{
var blogContent = content.GetValue<string>("bodyContent");
var parsedBodyText = keyphraseContentParser.ReplaceKeyphrasesWithLinks(blogContent, keyphrases);
content.SetValue("bodyContent", parsedBodyText);
}
}
}
}
The ContentServiceOnSaving method allows us to intercept any save event in Umbraco. Afterwhich we check our incoming content to see if it's of the type we're expecting - in this example 'NewsArticle' - and if it is, then target the 'bodyContent' section, parse this with our 'KeyphraseContentParser', and swap the current 'bodyContent' with the parsed 'bodyContent'.
Create a Keyphrase parser to swap keyphrases for internal/external links
public class KeyphraseContentParser
{
public string ReplaceKeyphrasesWithLinks(string htmlContent, IEnumerable<Keyphrase> keyphrases)
{
var parsedHtmlStringBuilder = new StringBuilder(htmlContent);
foreach (var keyphrase in keyphrases)
{
if (htmlContent.CaseContains(keyphrase.Phrase, StringComparison.OrdinalIgnoreCase))
{
var index = 0;
do
{
index = parsedHtmlStringBuilder.ToString()
.IndexOf(keyphrase.Phrase, index, StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
var keyphraseSuffix = parsedHtmlStringBuilder.ToString(index, keyphrase.Phrase.Length + 4);
var keyPhraseFromContent = parsedHtmlStringBuilder.ToString(index, keyphrase.Phrase.Length);
var keyphraseTarget = "_blank";
if (keyphrase.Link.StartsWith("/"))
{
keyphraseTarget = "_self";
}
var keyphraseLinkReplacement = String.Format("<a href='{0}' target='{1}'>{2}</a>",
keyphrase.Link, keyphraseTarget, keyPhraseFromContent);
if (!keyphraseSuffix.Equals(String.Format("{0}</a>", keyPhraseFromContent)))
{
parsedHtmlStringBuilder.Remove(index, keyPhraseFromContent.Length);
parsedHtmlStringBuilder.Insert(index, keyphraseLinkReplacement);
index += keyphraseLinkReplacement.Length;
}
else
{
var previousStartBracket = parsedHtmlStringBuilder.ToString().LastIndexOf("<a", index);
var nextEndBracket = parsedHtmlStringBuilder.ToString().IndexOf("a>", index);
parsedHtmlStringBuilder.Remove(previousStartBracket, (nextEndBracket - (previousStartBracket - 2)));
parsedHtmlStringBuilder.Insert(previousStartBracket, keyphraseLinkReplacement);
index = previousStartBracket + keyphraseLinkReplacement.Length;
}
}
} while (index != -1);
}
}
return parsedHtmlStringBuilder.ToString();
}
}
It's probably easiest to step through the above code, but fundamentally the parser has to:
a) find and wrap all keyphrases, ignoring case, with a link to an internal CMS, or external web resource.
b) handle an already parsed html string to both leave links in place and not create nested links.
c) allow CMS keyphrase changes to be updated in the parsed html string.
The blog of this, as well as the github code can be found from the links in the previous post.
Ok, so after finding some excellent helper posts and digging around I came up with the solution, which I've written about here:
http://frazzledcircuits.blogspot.co.uk/2015/03/umbraco-7-automatic-keyphrase.html
And the source code is here:
https://github.com/AdTarling/UmbracoSandbox

cshtml c# not listening to assigned id for some reason?

So I have a basic CRUD I am working on, and Im trying to get Jquery validation working on it. I have it almost all set up except I need to give my form an id. I am using cshtml in visual studio and try to assign the id to the form using the following code:
#using (Html.BeginForm( new { id = "daftform" })) {
#Html.ValidationSummary(true)
<fieldset>
<legend>News</legend>
However the generated html looks like this:
<form action="/News/Create/daftform" method="post"> <fieldset>
<legend>News</legend>
I am pretty sure this is how to assign an id to an element as I use this method to assign classes in a similar way. Can anyone tell me where im going wrong?
I just want it to assign 'daftform' as an id not as an action.
Sorry if its a simple answer, fairly new to c#.
Use this overload
public static MvcForm BeginForm(
this HtmlHelper htmlHelper,
string actionName,
string controllerName,
FormMethod method,
Object htmlAttributes
)
So your code can be changed to
#using (Html.BeginForm("Create","News",FormMethod.Post, new { id = "daftform" }))
{
//form elements
}
This will create a form tag with Id property set to "daftform"
#using (Html.BeginForm(,"actionname","controllername",formmethod.post/get, new {id = "yourId"})
is the way to go

Is it a Bad idea to use `<%=` in a WebControl

Recently I've started using <%= more often in my Web Controls. Typically I'll set String properties in the Code Behind and then spit them out onto the form.
Is this a bad idea?
eg.
Code Behind:
Properties:
public string TheTitle { get; set; }
public string TheBody { get; set; }
public ContentItem TheContent { get; set; }
public string ContentId {
get
{ return "content" + (TheContent != null) ? TheContent.Id.ToSTring() : "0"; }
}
Page_Load:
TheTitle = TheContentItem.Title;
TheBody = TheContentItem.Body;
On Page:
<div id='<%= ContentID %>'>
<h2 class='title'><%= TheTitle ?? "No Title" %></h2>
<p><%= TheBody %></p>
</div>
It is only a problem when the data is not validated.
Using .NET 4's <%: TheBody %> syntax is an effective way to encode potentially-untrusted data. In earlier versions of the framework, you can use <%= HttpUtility.HtmlEncode(TheBody) %> to the same effect.
It is bad if the data comes from an user input as your site will be vulnerable to XSS.
No it's not a problem* because it will be scoped to your control. You don't have to worry about anything conflicting in your case but if you were writing out server controls with IDs you could run into problems.
How your using it, ok (*assuming you have cleaned the data before assigning it to your variable). Just keep in mind there are times when it can be an issue with duplication of IDs, controls, scripts, etc...
Edit: Before assigning it to your varible you could use HttpUtility.HtmlEncode or if you using ASP.NET 4 you can use the <%: syntax depending on what you are outputting. This fall under the same rules as doing it an aspx, it's ok but you just need to be careful. This is also how much of ASP.NET MVC is used. The views can be literred with <%= and <%: Obviously using any type of encoding on any HTML itself would not be useful.

Setting html attribute that is reserved keyword in Html.CheckBoxFor in ASP.NET MVC

I am using the HtmlHelper to create a checkbox in my view like so:
<%= Html.CheckBoxFor(model => model.SeatOnly, new { checked = "checked" })%>
However, an error is being thrown as checked is a reserved keyword. I have found a couple of people saying that you must use the 'reserved word prefix' and simply put an uderscore in front of the attribute like so:
<%= Html.CheckBoxFor(model => model.SeatOnly, new { _checked = "checked" })%>
This does not generate an error but in the generated html the attribute is actually '_checked' which means it doesn't work (if I use firebug and remove the underscore the attribute then takes effect).
Does anyone know a way around this while still using CheckBoxFor?
Thanks
<%= Html.CheckBoxFor(model => model.SeatOnly, new { #checked = "checked" })%>
You need to prefix it with an '#' :-)
Update
Just tested with the following and works. Try this ...
Model
public class MyModel
{
public bool SomeBooleanValue { get; set; }
public string Title { get; set; }
}
View (snipped to only the important bits)
<%= Html.TextBoxFor(x => x.Title) %>
<%= Html.CheckBoxFor(x => x.SomeBooleanValue, new { #checked = "checked" }) %>
I had the same problem: the "checked" attribute was always omitted in the html code. i solved the problem by passing "true" into the model. snippet looks like this:
MyModel model = new MyModel();
model.SomeBooleanValue = true;
As you connect the Checkbox with the Model the checkbox always shows the value the model has. If the value in your model is "false" the checkbox will always be unchecked.
Maybe this helps ..
Use #checked
Also occurs with the attribute class (reserved word), them use #class
I couldn't figure out a way to get the HtmlHelper to register the checked attribute so I simple used Html.CheckBox("SeatOnly", true) instead. This way the property in my view model is still being set and the checkbox is checked.
This is fine but I would still be interested in knowing why #checked doesn't work in the same way as #class.

Categories