I need to get the first initial of the first name
I have the following:
#Html.DisplayFor(modelItem => item.FirstName.Substring(1,1))
I get the following error though when the program tries to run it:
Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.
Simply add a property to your view model:
public string FirstName { get; set; }
public string FirstLetterOfName
{
get
{
// TODO: a minimum amount of error checking
// might be good here for cases when FirstName
// is null or an empty string
return this.FirstName.Substring(1, 1);
}
}
and then:
#Html.DisplayFor(modelItem => item.FirstLetterOfName)
And if you now tell me that you are not using view models but are directly passing your domain entities to your views (which is very bad design), you might use a custom template:
#Html.DisplayFor(modelItem => item.FirstName, "FirstLetter")
and then you define a custom display template ~/Views/Shared/DisplayTemplates/FirstLetter.cshtml with the following contents:
#ViewData.TemplateInfo.FormattedModelValue.Substring(1, 1)
You can't do this, because the mapping to your abject wont work like this. Like the exception message tells you you can only use it with properties.
You can however write your own HtmlHelper extension to do what you want
public static class HtmlHelpers
{
public static MvcHtmlString CustomDisplayFor(this HtmlHelper<Person> htmlHelper, Expression<Func<Person, string>> expression)
{
var customBuildString = string.Empty;
//Make your custom implementation here
return MvcHtmlString.Create(customBuildString);
}
}
Before substring, check the length of the string, else you will get index out of range exception;
#MvcHtmlString.Create(item.title.Length > 20 ? #item.title.ToString().Substring(0, 20):#item.title.ToString())
Related
Let's say I have a test class like this:
public class TestClass
{
public Properties[] TestProperties { get; set; }
public Guid Id { get; set; }
public TestClass(Properties[] testProperties)
{
Id = Guid.NewGuid();
TestProperties = testProperties;
}
}
And a Properties class as follows:
public class Properties
{
public Guid Id { get; set; }
public string Name { get; set; }
public Properties(string name)
{
Name = name;
Id = Guid.NewGuid();
}
}
I need to validate that none of my properties Name at the TestProperties array is null, like this:
public class TestValidator : AbstractValidator<TestClass>
{
public TestValidator()
{
RuleForEach(x => x.TestProperties)
.Must(y => y.Name != string.Empty && y.Name != null)
.WithMessage("TestPropertie at {CollectionIndex}, can't be null or empty");
}
}
But instead of returning the position of the failing property, at the validation message, I would like to return it's Id, how can I do so?
Yes, using the default validators it's possible to inject other property values from the objects into the message.
This can be done by using the overload of WithMessage that takes a
lambda expression, and then passing the values to string.Format or by
using string interpolation.
Source
There are a couple of ways you can do it. Firstly, as per your current implementation using Must:
public class TestClassValidator : AbstractValidator<TestClass>
{
public TestClassValidator()
{
RuleForEach(x => x.TestProperties)
.Must(y => !string.IsNullOrEmpty(y.Name))
.WithMessage((testClass, testProperty) => $"TestProperty {testProperty.Id} name can't be null or empty");
}
}
I try to avoid using Must when possible, if you stick to using the built-in validators you stand a better chance of client-side validation working out of the box (if you're using it in a web app). Using ChildRules allows you to use the built-in validators and also get the benefit of using the fluent interface:
public class TestClassValidator : AbstractValidator<TestClass>
{
public TestClassValidator()
{
RuleForEach(x => x.TestProperties)
.ChildRules(testProperties =>
{
testProperties.RuleFor(testProperty => testProperty.Name)
.NotNull()
.NotEmpty()
.WithMessage(testProperty => $"TestProperty {testProperty.Id} name can't be null or empty");
});
}
}
ChildRules doco
I've included the NotNull() validator for verbosity/alignment with the custom error message, however it's not needed as NotEmpty() will cover the null or empty case.
Finally if it was me I'd probably create a separate validator for the Properties type (should this be Property?) and use SetValidator to include it. Splits up the validation concerns, defines the validation for a type once and makes the rules reusable, and makes the validators easier to test. I'm not going to cover that here as that feels beyond the scope of this question but the links below give examples on how to do it.
Child validator doco (SetValidator usage) here and here
Working samples of the above including tests can be found here.
I approached this a little differently, because I wanted a more reusable solution. (I'm validating many different classes in similar ways). Putting the message identification inside the extension with Must<> ties you to the type and could lead to copy&paste. Instead, I pass as an argument to the validation, a Func that returns an identifying string and lets the caller decide how to identify the object being validated.
public static IRuleBuilderOptions<T, string> IsValidStringEnumAllowNullable<T>(this IRuleBuilder<T, string> ruleBuilder, IList<string> validValues, Func<T,string> identifierLookup)
{
return ruleBuilder.Must((rootObject, testValue, context) =>
{
context.MessageFormatter.AppendArgument("AllowableValues", string.Join(", ", validValues));
context.MessageFormatter.AppendArgument("Identifier", identifierLookup(rootObject));
return string.IsNullOrEmpty(testValue) || validValues.Contains(testValue, StringComparer.Ordinal);
}).WithMessage("{Identifier}{PropertyName} with value {PropertyValue} must be one of the allowable values: {AllowableValues}, or null or empty string");
}
And then the calling code where I tell the specific validation message 'how' to identify the object for messaging:
base.RuleForEach(rq => rq.Thingies).ChildRules(x =>
{
x.RuleFor(f => f.MyProperty).IsValidStringEnumAllowNullable(ValidationStrings.AnArrayOfAllowedValues, f => $"Thing[{f.Id}] ");
});
The result of this code is
Thing[1234] My Property with value asdf must be one of the allowable values: Value1, ValidValue2, Somethingelse, or null or empty string
I have a project written in C# on the top on ASP.NET MVC 5 framework. I am trying to decouple my views from my view model so I can make my views reusable. With the heavy use of EditorTemplates I am able to create all of my standard views (i.e create, edit and details) by evaluating the ModelMetadata and the data-annotation-attributes for each property on the model, then render the page. The only thing that I am puzzled with is how to render the Index view.
My index view typically accepts an IEnumerable<object> or IPagedList<object> collection. In my view, I want to be able to evaluate the ModelMetadata of a each object/record in the collection to determine if a property on the object should be displayed or not.
In another words my view-model will look something like this
public class DisplayPersonViewModel
{
public int Id{ get; set; }
[ShowOnIndexView]
public string FirstName { get; set; }
[ShowOnIndexView]
public string LastName { get; set; }
[ShowOnIndexView]
public int? Age { get; set; }
public string Gender { get; set; }
}
Then my Index.cshtml view will accepts IPagedList<DisplayPersonViewModel> for each record in the collection, I want to display the value of the property that is decorated with ShowOnIndexView attribute.
Typically I would be able to do that my evaluating the ModelMetadata in my view with something like this
#model IPagedList<object>
#{
var elements = ViewData.ModelMetadata.Properties.Where(metadata => !metadata.IsComplexType && !ViewData.TemplateInfo.Visited(metadata))
.OrderBy(x => x.Order)
.ToList();
}
<tr>
#foreach(var element in elements)
{
var onIndex = element.ContainerType.GetProperty(element.PropertyName)
.GetCustomAttributes(typeof(ShowOnIndexView), true)
.Select(x => x as ShowOnIndexView)
.FirstOrDefault(x => x != null);
if(onIndex == null)
{
continue;
}
#Html.Editor(element.PropertyName, "ReadOnly")
}
</tr>
Then my controller will look something like this
public class PersonController : Controller
{
public ActionResult Index()
{
// This would be a call to a service to give me a collection with items. but I am but showing the I would pass a collection to my view
var viewModel = new List<DisplayPersonViewModel>();
return View(viewModel);
}
}
However the problem with evaluating ModelMetadata for the IPagedList<DisplayPersonViewModel> is that it gives me information about the collection itself not about the generic type or the single model in the collection. In another words, I get info like, total-pages, items-per-page, total-records....
Question
How can I access the ModelMetadata info for each row in the collection to be able to know which property to display and which not to?
I will preface this answer by recommending you do not pollute your view with this type of code. A far better solution would be to create a custom HtmlHelper extension method to generate the html, which gives you far more flexibility, can be unit tested, and is reusable.
The first thing you will need to change is the model declaration in the view which needs to be
#model object
otherwise you will throw this exception (List<DisplayPersonViewModel> is not IEnumerable<object> or IPagedList<object>, but it is object)
Note that it is not clear if you want the ModelMetadata for the type in the collection or for each item in the collection, so I have included both, plus code that gets the type
#model object
#{
var elements = ViewData.ModelMetadata.Properties.Where(metadata => !metadata.IsComplexType && !ViewData.TemplateInfo.Visited(metadata)).OrderBy(x => x.Order).ToList();
// Get the metadata for the model
var collectionMetaData = ViewData.ModelMetadata;
// Get the collection type
Type type = collectionMetaData.Model.GetType();
// Validate its a collection and get the type in the collection
if (type.IsGenericType)
{
type = type.GetInterfaces().Where(t => t.IsGenericType)
.Where(t => t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
.Single().GetGenericArguments()[0];
}
else if (type.IsArray)
{
type = type.GetElementType();
}
else
{
// its not a valid model
}
// Get the metadata for the type in the collection
ModelMetadata typeMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, type);
}
....
#foreach (var element in collectionMetaData.Model as IEnumerable))
{
// Get the metadata for the element
ModelMetadata elementMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => element, type);
....
Note that using reflection to determine if the attribute exists in each iteration of your loop is inefficient, and I suggest you do that once (based on the typeMetadata). You could then (for example) generate an array of bool values, and then use an indexer in the loop to check the value). A better alternative would be to have your ShowOnIndexView attribute implement IMetadataAware and add a value to the AdditionalValues of ModelMetadata so that var onIndex code is not required at all. For an example of implementing IMetadataAware, refer CustomAttribute reflects html attribute MVC5
Currently I am trying to set dynamic the DisplayName of a property but I cannot find a way how get information of current property inside an attribute.
This is what I want to achieve:
Desired Outcome
My Attribute
public class DisplayNameFromPropertyAttribute : DisplayNameAttribute
{
public DisplayNameFromPropertyAttribute(string propertyName)
: base(GetDisplayName(propertyName))
{
}
private string GetDisplayBame(string propertyName)
{
// Get the value from the given property
}
}
My Model
I am trying to read the value from MyDisplayName into my custom DisplayNameFromProperty attribute
public class MyAwesomeModel
{
public string MyDisplayName { get; set; }
[Required]
[DisplayNameFromProperty("MyDisplayName")]
public string MyValue { get; set; }
}
My Page
#Html.LabelFor(model => model.MyValue)
#Html.TextBoxFor(model => model.MyValue)
#Html.ValidationMessageFor(model => model.MyValue)
Question
Currently I cannot find any reference on internet doing the same. Can someone help me out?
If this is not possible: What are the alternatives to achieve same result?
The ComponentModel.DataAnnotations validation attributes should use my custom display name
The #Html.LabelFor(model => model.MyValue) should use my custom display name
You can create a cusom HTML extension method that lets you do #Html.DictionaryLabelFor(x=>x.Property) and pull it from a dictionary
public static IHtmlString DictionaryLabelFor<TModel, TValue>(
this HtmlHelper<TModel> html,
Expression<Func<TModel, TValue>> expression, string text = null, string prefix = null)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var displayName = metadata.DisplayName;
if (string.IsNullOrWhiteSpace(text))
{
// Your code to get the label via reflection
// of the object
string labelText = "";
return html.Label(prefix + metadata.PropertyName, labelText);
}
else
{
return html.Label(prefix + metadata.PropertyName, text);
}
}
Overriding the properties on this works, only thing missing is the custom html attributes which wasn't needed when I wrote it
The validation error message is slightly different, you should always write custom errors for these fields so you can rely on them in a resx, you look at the modelstate for the (prefix + key) to get the errors, then get the translated value for each case.
You're best avoiding overwriting the standard HTML call as you'll be making excess calls where not needed elsewhere.
When you have that working and understand it the error messages section is pretty trivial to write in yourself, depends how you want to do the formatting on errors. I'm not writing it in as it's basically doing everything for you and if you don't understand how it works you'll be SOL when it comes to writing further extensions
My model has an property whcih I assigned a ReadOnly tag. My intention is to set a propery to readonly true or false depending of a condition like
class Test {
static bool test() { // It is my model
// My logical response ...
return true;
}
[ReadOnly(test)]
datetime prop {get; set;}
}
using this model I get the error message:
Error 7 An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter
Could you, pleaee, get me an idea for this?
=====================================================================================
Solution like answer 3:
Inside Template:
cshtml:
...
#if (Model.condition) {
<td>#Html.EditorFor(m => m.prop)</td>
} else {
<td>#Html.DisplayFor(m => m.prop)</td>
}
...
It will be inside the template.
Inside Model in the copmnstructor I set the condition of the property condition:
class XX {
public condition { get; set; } // not necessary readonly, I do the logical inside the template.
public datetime prop {get; set; }
public XX (bool _condition) {
condition = _condition;
}
}
You cannot use something other than described in the error message as the parameter for attributes.
It is a sad true, but still a true.
Only this:
[ReadOnly(5)]
[ReadOnly("string")] // Or other type (int/long/byte/etc..) which can be used with const keyword.
[ReadOnly(Enums.SomeValue)]
[ReadOnly(typeof(SomeType))]
[ReadOnly(new string[] { "array", "of", "strings"} )]
So unfortunately, you wont succeed making custom parameter type:
class ReadOnlyAttribute { ReadOnlyAttribute(MyClass foo) { ... } }
One alternative would be to do it within the get/set, something like:
class test
{
static bool test() {
...
}
private datetime prop;
public datetime Prop
{
get { return prop; }
set
{
if (test)
prop = value;
else
// Ignore, throw exception, etc.
}
}
}
The metadata for the model (which includes your IsReadOnly) is created by the Model Metadata providers. This providers only have information about data types, and property names, but not about the concrete values of the properties of an instance of the model. So the metadata can not depend on the value of a property or method of the model class. (So implementing a Custom ModelMetada Provider wouldn't solve your problem).
Then, you have to find an alternative, hacky, way to do it:
Create a view model with two properties, the original, without the readonly attribute and an additional readonly property, decorated with the readonly attribute.
In the view, decide which of the two to show.
public class MyModel
{
public DateTime MyProperty { get; set;}
[ReadOnly]
public DateTime MyPropertyRo { get; set;}
}
If you want to recover the posted values, the editable version should use the original property in the Telerik control. The non-editable version should use the readonly property in the Telerik control, and the original property in a hidden-field, so that you can recover it in the post.
#if (Model.MyPropertyIsReadOnly)
{
#Html.HiddenFor(m => m.Property)
#Html.TelerikEditorFor(m => m.PropertyRo ...)
}
else
{
#Html.TelerikEditorFor(m => m.Property ...)
}
If you have to do this in many different views, you can create an Html helper (extension method for Html), which receives the 3 properties and includes the last sample code.
Finally, it would be even better to make a custom Editor template, but that's much harder to do if you don't have experience.
There is still another option: contact telerik, and ask them to implement a version of their control which receives a readonly parameter, and does this automatically for you. I think it must be really easy for them to implement it. So, if you're lucky enough...
I'm using ASP.NET MVC3 with Razor and C#. I am making a form builder of sorts, so I have a model that has a collection of the following object:
public class MyFormField
{
public string Name { get; set; }
public string Value { get; set; }
public MyFormType Type { get; set; }
}
MyFormType is just an enum that tells me if the form field is a checkbox, or textbox, or file upload, or whatever. My editor template looks something like this (see the comment):
~/Views/EditorTemplates/MyFormField.cshtml
#model MyFormField
#{
switch (Model.Type)
{
case MyFormType.Textbox:
#Html.TextBoxFor(m => m.Value)
case MyFormType.Checkbox:
#Html.CheckBoxFor(m => m.Value) // This does not work!
}
}
I tried casting/converting the m.Value to a bool in the lambda expression for CheckBoxFor(), but that threw an error. I would just manually construct a checkbox input, but CheckBoxFor() seems to do two things that I can't seem to replicate:
Creates a hidden input that somehow gets populated by the checkbox. This appears to be what the model binder picks up.
Generates the name form the object so that the model binder gets the value into the right property.
Does anyone know a way around using CheckBoxFor() on a string, or a way to replicate its functionality manually, so that I can make this work?
You could also add a property on your viewmodel:
public class MyFormField
{
public string Name { get; set; }
public string Value { get; set; }
public bool CheckBoxValue
{
get { return Boolean.Parse(Value); }
}
public MyFormType Type { get; set; }
}
Your view would be something like this:
#model MyFormField
#{
switch (Model.Type)
{
case MyFormType.Textbox:
#Html.TextBoxFor(m => m.Value)
case MyFormType.Checkbox:
#Html.CheckBoxFor(m => m.CheckBoxValue) // This does work!
}
}
Use Boolean.TryParse if you want to avoid exceptions.
One way is to create your own htmlhelper extension method.
public static MvcHtmlString CheckBoxStringFor<TModel>(this HtmlHelper<TModel> html, Expression<Func<TModel, string>> expression)
{
// get the name of the property
string[] propertyNameParts = expression.Body.ToString().Split('.');
string propertyName = propertyNameParts.Last();
// get the value of the property
Func<TModel, string> compiled = expression.Compile();
string booleanStr = compiled(html.ViewData.Model);
// convert it to a boolean
bool isChecked = false;
Boolean.TryParse(booleanStr, out isChecked);
TagBuilder checkbox = new TagBuilder("input");
checkbox.MergeAttribute("id", propertyName);
checkbox.MergeAttribute("name", propertyName);
checkbox.MergeAttribute("type", "checkbox");
checkbox.MergeAttribute("value", "true");
if (isChecked)
checkbox.MergeAttribute("checked", "checked");
TagBuilder hidden = new TagBuilder("input");
hidden.MergeAttribute("name", propertyName);
hidden.MergeAttribute("type", "hidden");
hidden.MergeAttribute("value", "false");
return MvcHtmlString.Create(checkbox.ToString(TagRenderMode.SelfClosing) + hidden.ToString(TagRenderMode.SelfClosing));
}
The usage is the same as CheckBoxFor helper (e.Value is a string)
#Html.CheckBoxStringFor(e => e.Value)
Use the Checkbox, this simple way works fine
#Html.CheckBox("IsActive", Model.MyString == "Y" ? true : false)
I had this problem as well but was unable to modify the view model. Tried mdm20s solution but as i suspected it does not work on collection properties (it does not add the indexes to the names and ids like the native html helpers). To overcome this you can use the Html.CheckBox instead. It adds the proper indexes and you can pass the value of the checkbox yourself.
If you really want to use an expression you can always write a wrapper similar to mdm20s but replace everything after the TryParse with
return Html.CheckBox("propertyName", isChecked). Obviously you will need to add using System.Web.Mvc.Html as well.