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")
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 need to validate a byte[] in my model as Required but whenever I use Data Annotation [Required] on it, it won't do anything. Even if i choose a file it outputs error message.
Details:
Model:
Public class MyClass
{
[Key]
public int ID {get; set;}
[Required]
public string Name {get; set;}
public byte[] Image {get; set;}
[Required]
public byte[] Template {get; set;}
}
View:
<div class="editor-label">
<%:Html.LabelFor(model => model.Image) %>
</div>
<div class="editor-field">
<input type="file" id="file1" name="files" />
</div>
<div class="editor-label">
<%:Html.Label("Template") %>
</div>
<div class="editor-field">
<input type="file" id="file2" name="files"/>
</div>
<p>
<input type="submit" value="Create" />
</p>
I've looked around the posts and notice people use custom validation but they have used HttpPostedFileBase as types of files instead of byte[] like me, for some reason when I try to use the same it errors with a missing ID for it... Even though the model has it's own ID declared.
EDIT:
Context - OnModelCreating additions for Report
modelBuilder.Entity<Report>().Property(p => p.Image).HasColumnType("image");
modelBuilder.Entity<Report>().Property(p => p.Template).HasColumnType("image");
note that i had to put image as ColumnType because of Byte array truncation to a length of 4000. error.
Controller:
public ActionResult Create(Report report, IEnumerable<HttpPostedFileBase> files)
{
if (ModelState.IsValid)
{
db.Configuration.ValidateOnSaveEnabled = false;
if (files.ElementAt(0) != null && files.ElementAt(0).ContentLength > 0)
{
using (MemoryStream ms = new MemoryStream())
{
files.ElementAt(0).InputStream.CopyTo(ms);
report.Image = ms.GetBuffer();
}
}
if (files.ElementAt(1) != null && files.ElementAt(1).ContentLength > 0)
{
using (MemoryStream ms1 = new MemoryStream())
{
files.ElementAt(1).InputStream.CopyTo(ms1);
report.Template = ms1.GetBuffer();
}
}
db.Reports.Add(report);
db.SaveChanges();
//Temporary save method
var tempID = 10000000 + report.ReportID;
var fileName = tempID.ToString(); //current by-pass for name
var path = Path.Combine(Server.MapPath("~/Content/Report/"), fileName);
files.ElementAt(1).SaveAs(path);
db.Configuration.ValidateOnSaveEnabled = true;
return RedirectToAction("Index");
}
Hopefully you may notice what I'm missing.
The RequiredAttribute checks for null and an empty string.
public override bool IsValid(object value)
{
if (value == null)
return false;
string str = value as string;
if (str != null && !this.AllowEmptyStrings)
return str.Trim().Length != 0;
else
return true;
}
This works fine if your byte array is null, but you probably want to check for an empty array as well (without seeing how you are assigning the value of your Template property, I can only guess that this is the case). You can define your own required attribute that does this check for you.
public class RequiredCollectionAttribute : RequiredAttribute
{
public override bool IsValid(object value)
{
bool isValid = base.IsValid(value);
if(isValid)
{
ICollection collection = value as ICollection;
if(collection != null)
{
isValid = collection.Count != 0;
}
}
return isValid;
}
}
Now just replace the Required attribute on your Template property with our new RequiredCollection attribute.
[RequiredCollection]
public byte[] Template {get; set;}
I've looked around the posts and notice people use custom validation
but they have used HttpPostedFileBase as types of files instead of
byte[] like me..
Do you want to bind the posted file to a byterray field in the model? If yes you have to go for a custom model binder.
ASP.NET MVC already has a built-in model binder for byte array so you can easily extend that as suggested in this post.
public class CustomFileModelBinder : ByteArrayModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var file = controllerContext.HttpContext.Request.Files[bindingContext.ModelName];
if (file != null)
{
if (file.ContentLength != 0 && !String.IsNullOrEmpty(file.FileName))
{
var fileBytes = new byte[file.ContentLength];
file.InputStream.Read(fileBytes, 0, fileBytes.Length);
return fileBytes;
}
return null;
}
return base.BindModel(controllerContext, bindingContext);
}
}
protected void Application_Start()
{
...
ModelBinders.Binders.Remove(typeof(byte[]));
ModelBinders.Binders.Add(typeof(byte[]), new CustomFileModelBinder());
}
Now the uploaded file will directly come and sit as bytearray in the properties.
I've altered my Create method, and this is what I've come up with. Seems to work fine, but...
if (files.ElementAt(1) != null && files.ElementAt(1).ContentLength > 0)
{
using (MemoryStream ms1 = new MemoryStream())
{
files.ElementAt(1).InputStream.CopyTo(ms1);
report.Template = ms1.GetBuffer();
}
}
else // this part of code did the trick, although not sure how good it is in practice
{
return View(report);
}
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);
}
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 />
}
}
}
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>
}
}
}