How can i hide the attribute:
[Display(Name = "dspName")]
alternatively the variable name from my variable in the (razor) view?
My problem is that I have defined a custom template for booleans that views the boolean like:
varname/displayName: 'box'
If I create the view with:
#Html.EditorForModel(Model)
the Result is:
varname/displayName
varname/displayName: 'box'
Result in Browser:
edit: my BooleanTemplate
#model System.Boolean?
#{
string name = string.Empty;
if (!string.IsNullOrWhiteSpace(ViewData.ModelMetadata.DisplayName))
{
name =ViewData.ModelMetadata.DisplayName;
}
else
{
name = ViewData.ModelMetadata.PropertyName;
}
}
#name:
#Html.CheckBox("", Model.HasValue ? Model : Model.Value)
The additional label you are seeing is baked into the default editor template for the Object class. So you have two possibilities:
Use #Html.EditorFor(x => x.SomeBoolProperty) and so on for each property instead of #Html.EditorForModel()
Modify the default editor template of the object class (EditorTemplates/Object.cshtml) to remove the label (notice the part I have put in comments):
#if (ViewData.TemplateInfo.TemplateDepth > 1)
{
#ViewData.ModelMetadata.SimpleDisplayText
}
else
{
foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForEdit && !ViewData.TemplateInfo.Visited(pm)))
{
if (prop.HideSurroundingHtml)
{
#Html.Editor(prop.PropertyName)
}
else
{
#*if (!String.IsNullOrEmpty(Html.Label(prop.PropertyName).ToHtmlString()))
{
<div class="editor-label">#Html.Label(prop.PropertyName)</div>
}*#
<div class="editor-field">
#Html.Editor(prop.PropertyName)
#Html.ValidationMessage(prop.PropertyName, "*")
</div>
}
}
}
Related
I have some tabs in bootstrap which has to be set as active depending on the action being hit. I have sub-tabs as well which has to be set as active depending on the action being hit as well.
Here is an image of how it looks like:
So when a sub-tab is being active the parent tab has to be active too.
So I thought to create a new Attribute where I save a pageId for each action and depending on the pageId on the view I can set it to active or not:
Here is the attribute:
public class YcoPageId : Attribute
{
public YcoPageId(int pageId)
{
PageId = pageId;
}
public int PageId { get; }
}
Here is the action:
[YcoPageId(1)]
public ActionResult Admin()
{
return View();
}
For the view I want to create an extension method to see if the tab and sub-tabs shall be active or not!
Here is my code:
public static bool IsActive(this HtmlHelper htmlHelper, params int[] ids)
{
var viewContext = htmlHelper.ViewContext;
var action = viewContext....
//How to get the YcoPageId attribute from here and see the Id
//Here i will test if ids contain id but dont know how to get it...
}
If you think adding an id for each page is bad idea I think for my case I will use this id for other purposes as well because it will be like identifier for a specific action...
So my question is how can I get the attribute YcoPageId for current action in my extension method ?
The view will look like this:
<li class="#(Html.IsActive(1, 4, 5... etc)? "active" : "")">
<a href="#url">
<div class="row text-center">
<div class="col-md-12">
<i class="fa #fontAwesomeIcon fa-4x" aria-hidden="true"></i>
<br/>#menuName
</div>
</div>
</a>
</li>
If there is any better idea how to solve this issue please go ahead!
Here is my solution to this problem:
First created a actionfilter attribute like below:
public class YcoPageIdAttribute : ActionFilterAttribute
{
public YcoPageIdAttribute(int pageId)
{
PageId = pageId;
}
public int PageId { get; }
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.Result is ViewResult)
{
filterContext.Controller.TempData[DomainKeys.ViewPageId] = PageId;
}
else
{
throw new Exception("Only ViewResult has unique id");
}
base.OnActionExecuted(filterContext);
}
}
Then my action would look like this one:
[YcoPageId(1)]
public ActionResult Admin()
{
return View();
}
I created an extension method like below:
public static bool IsActive(this HtmlHelper htmlHelper, params int[] ids)
{
var viewContext = htmlHelper.ViewContext;
return viewContext.TempData.ContainsKey(DomainKeys.ViewPageId) &&
int.Parse(viewContext.TempData.Peek(DomainKeys.ViewPageId).ToString()).In(ids);
}
Since I know the id of an action now I have only to put the code as below in the view:
<li class="#(Html.IsActive(1)? "active" : "")">
<a href="#url">
<div class="row text-center">
<div class="col-md-12">
<i class="fa #fontAwesomeIcon" aria-hidden="true"></i>
<br /><small>#menuName</small>
</div>
</div>
</a>
</li>
I made another method on startup to check if I have actions with duplicated values like below:
public static void CheckForDuplicateActionId()
{
Assembly asm = Assembly.GetExecutingAssembly();
var controllerActionlist = asm.GetTypes()
.Where(type => typeof(Controller).IsAssignableFrom(type))
.SelectMany(type => type.GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly |
BindingFlags.Public))
.Where(m => !m.GetCustomAttributes(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute),
true).Any())
.Where(m => m.GetCustomAttribute<YcoPageIdAttribute>() != null)
.Select(
x =>
new
{
Controller = x.DeclaringType.Name,
Area = x.DeclaringType.FullName,
Action = x.Name,
ReturnType = x.ReturnType.Name,
Id = x.GetCustomAttribute<YcoPageIdAttribute>().PageId
})
.ToList();
var actionIds = controllerActionlist.Select(x => x.Id).ToList();
var actionIdsGrouped = actionIds.GroupBy(x => x).Where(x => x.Count() > 1).ToList();
if (!actionIdsGrouped.IsNullOrEmpty())
{
StringBuilder error = new StringBuilder("");
actionIdsGrouped.ForEach(actionId =>
{
var actions = controllerActionlist.Where(x => x.Id == actionId.Key);
actions.ForEach(a =>
{
error.Append(
$" | Id : {a.Id}, Action Name: {a.Action}, Controller Name : {a.Controller}, Location : {a.Area}. ");
});
});
var maxId = controllerActionlist.Max(x => x.Id);
error.Append(
"PLease consider changing the the duplicated id - Here are some options to choose from : Id {");
for (int i = 1, j = 1; i < maxId + 5; i++)
{
if (actionIds.Contains(i)) continue;
if (j < 5)
{
error.Append(i + ",");
j++;
}
else
{
error.Append(i + "}");
break;
}
}
throw new Exception(
$"There are more than one action duplicated with the same Id, The action data are as below : {error}");
}
}
Probably I will add all these data in database so I can identify an action from one id from database as well :)
Now it is working good.
If I understand correctly you are trying to create an id for each page/view and use it in the page/view to dynamically set css classes for setting menu tabs as active. If that is the case... Rather than trying to set the Ids in the Controller, how about creating a Shared View with only the following code - something like this....
In the View write the following Razor/C# code.
#{
var iPageId = 0
var sViewPath = ((System.Web.Mvc.BuildManagerCompiledView)ViewContext.View).ViewPath;
//for example
if (sViewPath.ToLower.IndexOf("admin") >= 0)
{
iPageId = 1;
}
else if (sViewPath.ToLower.IndexOf("dashboard") >= 0)
{
iPageId = 2;
}
else if (sViewPath.ToLower.IndexOf("vessels") >= 0)
{
iPageId = 3;
}
else if (sViewPath.ToLower.IndexOf("reports") >= 0)
{
iPageId = 4;
}
}
Render the Shared View in the primary view with the following snippet.
#Html.Partial("~/Views/Menu/_SharedViewName.cshtml")
Then you should be able to access the iPageId variable anywhere in the primary page/view(s) and set your CSS classes accordingly.
I have implemented a base view model for the configuration forms of different modules in my system.
In that base logic I'm iterating the reflected properties of the (user-)derived viewmodel and dynamically create a list of ConfigItem objects
Simplified my object looks as follows:
public class ConfigItem
{
public string Key { get; set; }
public object Value { get; set; }
public Type PropertyType { get; set; }
public ConfigType ConfigType { get; set; }
public string PostId { get; set; }
};
The "value" property can be an object of a simple or complex type (string, bool image, etc.).
In a nested view I'm rendering all ConfigItems in a loop (in order to decorate each with additional information like tooltips etc.).
For the binding of the posted form I'm setting the name attribute of the input fields so the values are bound back to the original's viewmodel properties. The validation of DataAnnotation attributes works on server-side.
Now to the problem:
As we all know ASP comes with the neat feature of rendering jquery unobtrusive validation attributes. Unfortunately I'm losing all my metadata when rendering ConfigItems instead of the original properties, thus ASP doesn't know about my attributes like e.g. "Required" or "StringLength". Does anyone have an idea how to somehow "carry-along" the property metadata?
The best thing would be that my ConfigItem's value could be an expression tree like of type Expression>, so I could carry along a "pointer" to the original property. But I couldn't figure out how to technically solve it. If someone could shed a little light on this (or perhaps have some other hints) I highly appreciate that. Thanks!
Update:
I'm rendering my ConfigItems with a little custom Html-Helper, where I evaluate the model state:
public static MvcHtmlString ConfigItem<TModel>(this HtmlHelper<TModel> htmlHelper, ConfigItem configItem, bool isEditMode = false)
{
if (isEditMode)
{
if (!htmlHelper.ViewData.ModelState.IsValidField(configItem.PostId))
{
var itemState = htmlHelper.ViewData.ModelState[configItem.PostId];
if (itemState.Errors.Count > 0)
{
configItem.ValidationMessage = string.Join("</br>", Array.ConvertAll(itemState.Errors.ToArray(), i => i.ErrorMessage));
}
}
return htmlHelper.Partial(EditorConfigItemPartialPath, configItem);
}
else
{
return htmlHelper.Partial(DisplayConfigItemPartialPath, configItem);
}
}
The ConfigItem EditorTemplate does something like this:
#model ConfigItem
<div class="value">
#switch (Model.DataType)
{
case ConfigDataType.String:
{
var value = Model.Value == null ? "" : Model.Value;
#Html.EditorFor(m => value, "string", Model.PostId)
break;
}
case ConfigDataType.Bool:
{
#Html.EditorFor(m => Model.Value, "", Model.PostId)
break;
}
case ConfigDataType.Image:
{
#Html.ConfigImage(Model.Key, m => Model.Value, Url.Action(ApplicationController.ActionUploadImage), Model.PostId)
break;
}
}
#if (!string.IsNullOrEmpty(Model.ValidationMessage))
{
<span class="#errorClass">
#Html.Raw(Model.ValidationMessage)
</span>
}
</div>
If your missing your Script your partial view, you can use below code:
Common function for loading script into partial view :
function loadScript(url, callback){
var script = document.createElement("script")
script.type = "text/javascript";
if (script.readyState){ //IE
script.onreadystatechange = function(){
if (script.readyState == "loaded" ||
script.readyState == "complete"){
script.onreadystatechange = null;
callback();
}
};
} else { //Others
script.onload = function(){
callback();
};
}
script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
}
and call this function into your switch statement like below :
#switch (Model.DataType)
{
case ConfigDataType.String:
{
var value = Model.Value == null ? "" : Model.Value;
#Html.EditorFor(m => value, "string", Model.PostId)
loadScript("~/Content/js/register.js", function(){
//initialization code
});
break;
}
case ConfigDataType.Bool:
{
#Html.EditorFor(m => Model.Value, "", Model.PostId)
loadScript("~/Content/js/register.js", function(){
//initialization code
});
break;
}
case ConfigDataType.Image:
{
#Html.ConfigImage(Model.Key, m => Model.Value, Url.Action(ApplicationController.ActionUploadImage), Model.PostId)
loadScript("~/Content/js/register.js", function(){
//initialization code
});
break;
}
}
replace ~/Content/js/register.js with your required JavaScript
hope it will help you.
Thanks
I have a mvc web project where I try to render a list of checkbox with the EditorFor extension method but the result just display the ids as text instead of of a list of checkbox.
Here is the code in the view:
<div id="permissions" class="tab-body">
#Html.Label("Permissions :")
#Html.EditorFor(x => Model.Permissions)
<br />
<br />
</div>
This is the property 'Permissions' of the object 'Model':
[DisplayName("Permissions")]
public List<PermissionViewModel> Permissions { get; set; }
And this is the PermissionViewModel:
public class PermissionViewModel
{
public int Id { get; set; }
public UserGroupPermissionType Name { get; set; }
public string Description { get; set; }
public bool IsDistributable { get; set; }
public bool IsGranted { get; set; }
}
And finally, this is the result in the browser:
<div id="permissions" class="tab-body" style="display: block;">
<label for="Permissions_:">Permissions :</label>
192023242526272829
<br>
<br>
</div>
Have you any idea why the html is not generated correctly? Missing dependencies? Conflict in dependencies? Web.Config configured not correctly?
Thank you very much for you help.
It looks as if you need to create an editor template for the "PermissionViewModel" class, as right now, MVC seems to be confused with how to make an editor for such a complex object.
In the folder where the view is being served from, add a folder called "EditorTemplates"
Then add a new partial view in that folder. The code should be:
#model IEnumberable<PermissionViewModel>
#foreach(var permission in Model)
#Html.EditorFor(x => x.Name)
#Html.EditorFor(x => x.Description)
#Html.EditorFor(x => x.IsDistributable)
#Html.EditorFor(x => x.IsGranted)
You will need to create an Editor Template for the Name class as well.
So now in your view you can call
<div id="permissions" class="tab-body">
#Html.Label("Permissions :")
#Html.EditorFor(x => Model.Permissions)
<br />
<br />
</div>
And MVC will know to use the editor template you just made for your permission.
A good resource for learning about editor templates is here: http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-1-introduction.html
Maybe you want to make something yourself?
public delegate object Property<T>(T property);
public static HtmlString MultiSelectListFor<TModel, TKey, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, IEnumerable<TKey>>> forExpression,
IEnumerable<TProperty> enumeratedItems,
Func<TProperty, TKey> idExpression,
Property<TProperty> displayExpression,
Property<TProperty> titleExpression,
object htmlAttributes) where TModel : class
{
//initialize values
var metaData = ModelMetadata.FromLambdaExpression(forExpression, htmlHelper.ViewData);
var propertyName = metaData.PropertyName;
var propertyValue = htmlHelper.ViewData.Eval(propertyName).ToStringOrEmpty();
var enumeratedType = typeof(TProperty);
//check for problems
if (enumeratedItems == null) throw new ArgumentNullException("The list of items cannot be null");
//build the select tag
var returnText = string.Format("<select multiple=\"multiple\" id=\"{0}\" name=\"{0}\"", HttpUtility.HtmlEncode(propertyName));
if (htmlAttributes != null)
{
foreach (var kvp in htmlAttributes.GetType().GetProperties()
.ToDictionary(p => p.Name, p => p.GetValue(htmlAttributes, null)))
{
returnText += string.Format(" {0}=\"{1}\"", HttpUtility.HtmlEncode(kvp.Key),
HttpUtility.HtmlEncode(kvp.Value.ToStringOrEmpty()));
}
}
returnText += ">\n";
//build the options tags
foreach (TProperty listItem in enumeratedItems)
{
var idValue = idExpression(listItem).ToStringOrEmpty();
var displayValue = displayExpression(listItem).ToStringOrEmpty();
var titleValue = titleExpression(listItem).ToStringOrEmpty();
returnText += string.Format("<option value=\"{0}\" title=\"{1}\"",
HttpUtility.HtmlEncode(idValue), HttpUtility.HtmlEncode(titleValue));
if (propertyValue.Contains(idValue))
{
returnText += " selected=\"selected\"";
}
returnText += string.Format(">{0}</option>\n", HttpUtility.HtmlEncode(displayValue));
}
//close the select tag
returnText += "</select>";
return new HtmlString(returnText);
}
My model class:
public class StatusList
{
public int StatusID {get;set;}
[UIHint("ByteCheckbox")]
public byte Active {get;set;}
}
In /Views/Shared/EditorTemplates I created a file called ByteCheckbox.cshtml
The editortemplate ByteCheckbox contains (My 3rd attempt):
#model byte
#if (Model == 1)
{
#Html.CheckBox("", true)
}
else
{
#Html.CheckBox("", false)
}
Doing this nicely renders a checkbox. When I change the checkbox status and try to save the changes the model validation complains that the value is 'false' (or 'true') instead of the expected 0 or 1.
How to modify the editortemplate to allow for the value to be translated?
Have you tried this?
<div class="editor-label">
#Html.LabelFor(model => model.Active)
</div>
<div class="editor-field">
#Html.CheckBoxFor(model => model.Active != 0)
#Html.ValidationMessageFor(model => model.Active)
</div>
You can do this in your model:
public class StatusList
{
public int StatusID {get;set;}
public byte Active {get;set;}
[NotMapped]
public bool ActiveBool
{
get { return Active > 0; }
set { Active = value ? 1 : 0; }
}
}
Don't use Html.CheckBox; instead use Html.EditorFor. You'll need to define a file called ByteCheckbox.cshtml in Views/Shared/EditorTemplates for this to work as well.
You can also use a custom model binder.
Here's a sample for a decimal, but you can do it for the byte type.
public class DecimalModelBinder : DefaultModelBinder {
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
dynamic valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult == null) {
return base.BindModel(controllerContext, bindingContext);
}
return ((string)valueProviderResult.AttemptedValue).TryCDec();
}
}
try this one
#model bool
<div class="editor-for">
#Html.CheckBox("", Model, new{#class="tickbox-single-line"})
<div>
Try this:
#Html.CheckBox("Model", "Model")
What about if I have a navigation property? I have a collection of another model in the model I'm trying to edit. I want to display a list of checkboxes for each object in the collection property. So far, this is what I tried...
#{
foreach (var category in ViewBag.Categories)
{
if (Model.Categories.Contains(category))
{
<input type="checkbox" name="selected-categories" value="category.CategoryId" checked="checked" />#category.Name
}
else
{
<input type="checkbox" name="selected-categories" value="#category.CategoryId" />#category.Name
}
}
}
But it fails with an EntityCommandExecutionException. In my if statement, how can I access the model.Categories like I do in something like #Html.EditorFor(model => model.Id)???
Using a strongly-typed View is the way I'd go. Create a ViewModel that contains your Model and use this ViewModel for the strongly-typed View.
Domain Model and ViewModel (simplified)
public class YourModel
{
string Category { get ; set ; }
}
public class YourViewModel
{
public List<string> PossibleCategories { get ; set ; }
public YourModel YourData { get ; set ; }
}
Then the View:
#model YourViewModel
#{
foreach (string CurrCategory in Model.PossibleCategories)
{
if (Model.YourData.Category == CurrCategory)
{
#Html.CheckBox(CurrCategory, new { #checked = "checked" })
#Html.Encode(CurrCategory) <br />
}
else
{
#Html.CheckBox(CurrCategory, false)
#Html.Encode(CurrCategory) <br />
}
}
}