Get scaffolder to generate fields in specific order - razor

I am trying to get asp.net core MVC to scaffold a Razor View with fields in a different order than the apparently default alphabetical order. I've got a simple model:
public class Application : EntityBase
{
[Display(Name = "Naam", Order = 1)]
public string Name { get; set; }
[Display(Name = "Omschrijving", Order = 2)]
public string Description { get; set; }
}
I want the scaffolder to generate a field for Name before Description. How to do this?
I've been trying to come up with a solution in the Razor template. The relevant code is:
...
IEnumerable<PropertyMetadata> properties = Model.ModelMetadata.Properties;
foreach (var property in properties)
{
if (property.Scaffold && !property.IsPrimaryKey && !property.IsForeignKey)
{
...
I was hoping that a property had an Order-property, so I could write something like
foreach (var property in properties.OrderBy(p => p.Order))
Any ideas?

So, after some (deep) digging I came up with a solution. As I allready customized the templates, it was acceptable to add yet another customization. I ended up creating a helper class ScaffoldHelpers.cs:
public class ScaffoldHelpers
{
public static IEnumerable<PropertyMetadata> GetPropertiesInDisplayOrder(string typename, IEnumerable<PropertyMetadata> properties)
{
Type type = Type.GetType($"{typename}, {typename.Split('.')[0]}");
SortedList<string, PropertyMetadata> propertiesList = new SortedList<string, PropertyMetadata>();
foreach (PropertyMetadata property in properties)
{
int order = 0;
if (type != null)
{
var member = type.GetMember(property.PropertyName)[0];
var displayAttribute = member.GetCustomAttribute<System.ComponentModel.DataAnnotations.DisplayAttribute>();
if (displayAttribute != null)
{
order = displayAttribute.Order;
}
}
propertiesList.Add($"{order:000} - {property.PropertyName}", property);
}
return propertiesList.Values.AsEnumerable();
}
}
This iterates over all the properties, and determines if a [Display()] attribute is specified. If so, it gets the value of the Order-parameter. If you don't specify this, the Order-property will be zero. Using a SortedList and making sure the key is ordered by the specified order, I'm able to easily return an IEnumerable<PropertyMetadata> in the desired order.
In the template, I needed to add #using for this helper-class. After that, I could insert the following into the template:
...
IEnumerable<PropertyMetadata> properties = Model.ModelMetadata.Properties;
// added:
properties = ScaffoldHelpers.GetPropertiesInDisplayOrder(Model.ViewDataTypeName, properties);
foreach (var property in properties)
{
if (property.Scaffold && !property.IsPrimaryKey && !property.IsForeignKey)
{
...
That's it!

Related

Table TagHelper for an IEnumerable Model (without using Reflection)

I'm trying to create a table tag helper that can parse columns and rows from the given model automatically.
This is how it should (theoretically be used):
<table for="#Model">
</table>
and this should automatically show column names and the rows.
Generating column names wasn't that difficult since I'm passing the model
[HtmlTargetElement("table", Attributes = "for")]
public class DataTableTagHelper :TagHelper
{
[HtmlAttributeName("for")]
public ModelExpression For { get; set; }
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
foreach (var item in For.Metadata.ElementMetadata.Properties)
{
// generate html for theader using item.Name
}
}
}
But getting the values of the model isn't as easy.
Is there a way to get the values of those properties?
I'm trying to avoid reflection, because I don't think generating HTML code though reflection with every request is a good idea.
We get the property's value by passing the model to its property's PropertyGetter.
public override void Process(TagHelperContext context, TagHelperOutput output)
{
foreach (var prop in For.Metadata.Properties)
{
var propertyName = prop.Name;
var propertyValue = prop.PropertyGetter(For.Model);
}
return Task.CompletedTask;
}
If the model implements IEnumerable, then we need to pass each item to its PropertyGetter.
public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
foreach (var item in For.Model as IEnumerable)
{
foreach (var prop in For.Metadata.ElementMetadata.Properties)
{
var name = prop.Name;
var value = prop.PropertyGetter(item);
}
}
return Task.CompletedTask;
}

Model Validation is not working in ASP.NET Core 2.0 [duplicate]

I have developed a custom validator Attribute class for checking Integer values in my model classes. But the problem is this class is not working. I have debugged my code but the breakpoint is not hit during debugging the code. Here is my code:
public class ValidateIntegerValueAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
int output;
var isInteger = int.TryParse(value.ToString(), out output);
if (!isInteger)
{
return new ValidationResult("Must be a Integer number");
}
}
return ValidationResult.Success;
}
}
I have also an Filter class for model validation globally in application request pipeline. Here is my code:
public class MyModelValidatorFilter: IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
if (context.ModelState.IsValid)
return;
var errors = new Dictionary<string, string[]>();
foreach (var err in actionContext.ModelState)
{
var itemErrors = new List<string>();
foreach (var error in err.Value.Errors){
itemErrors.Add(error.Exception.Message);
}
errors.Add(err.Key, itemErrors.ToArray());
}
actionContext.Result = new OkObjectResult(new MyResponse
{
Errors = errors
});
}
}
The model class with validation is below:
public class MyModelClass
{
[ValidateIntegerValue(ErrorMessage = "{0} must be a Integer Value")]
[Required(ErrorMessage = "{0} is required")]
public int Level { get; set; }
}
Can anyone please let me know why the attribute integer validation class is not working.
Model validation comes into play after the model is deserialized from the request. If the model contains integer field Level and you send value that could not be deserialized as integer (e.g. "abc"), then model will not be even deserialized. As result, validation attribute will also not be called - there is just no model for validation.
Taking this, there is no much sense in implementing such ValidateIntegerValueAttribute. Such validation is already performed by deserializer, JSON.Net in this case. You could verify this by checking model state in controller action. ModelState.IsValid will be set to false and ModelState errors bag will contain following error:
Newtonsoft.Json.JsonReaderException: Could not convert string to
integer: abc. Path 'Level', ...
One more thing to add: for correct work of Required validation attribute, you should make the underlying property nullable. Without this, the property will be left at its default value (0) after model deserializer. Model validation has no ability to distinguish between missed value and value equal to default one. So for correct work of Required attribute make the property nullable:
public class MyModelClass
{
[Required(ErrorMessage = "{0} is required")]
public int? Level { get; set; }
}

Change item queue in #Html.EnumDropDownListFor

There's this enum
public enum UserStatus
{
Employee = 0,
Evaluation,
Dismissed,
Registered,
}
And on view
#Html.EnumDropDownListFor(model => model.User.Metadata.Status)
So it show me Employee as default option and all other items with enum queue (E,E,D,R). But i'd like to show items in this queue (Evaluation, Registered, Employee, Dismissed) (Mainly Evaluation must be first).
I cant change the enum, and i cant set as default in GET controller (due to model realization).
Any ideas how solve this problem?
I don't think you can change the list during runtime on how it appears. The easiest way i can think to handle such problem ( where you can't change the sequence in which the enum values appear ) is to add attribute on each enum value that defines the sequence number and then extract all the items in a specific enum to create a list which can be binded to the view. This might be an extra work but would solve your issue.
Here's a sample code :
public class Sequence : Attribute
{
public int SequenceNum { get; set; }
}
public enum UserStatus
{
[Sequence(SequenceNum=3)]
Employee = 0,
[Sequence(SequenceNum = 2)]
Evaluation,
[Sequence(SequenceNum = 4)]
Dismissed,
[Sequence(SequenceNum = 1)]
Registered,
}
In your model class :
public IEnumerable<SelectListItem> ListData { get; set; }
public UserStatus Status { get; set; }
In your controller :
List<KeyValuePair<int,string>> KeyValueList = new List<KeyValuePair<int,string>>();
// Get all the enum values
var values = Enum.GetValues(typeof(UserStatus)).Cast<UserStatus>();
// Iterate through each enum value to create a keyvalue list
foreach (var item in values)
{
var type = typeof(UserStatus);
var memInfo = type.GetMember(item.ToString());
var attributes = memInfo[0].GetCustomAttributes(typeof(Sequence),false);
KeyValueList.Add(new KeyValuePair<int,string>(((Sequence)attributes[0]).SequenceNum,item.ToString()));
}
// Sort the keyvalue list based on the *SequenceNum* attribute
KeyValueList.Sort((firstPair, nextPair) =>
{
return nextPair.Value.CompareTo(firstPair.Value);
});
// Add SelectListItem collection to a model property - Apparently you can add the generate collection to a ViewData ( if you don't wish to create another property )
model.ListData = KeyValueList.Select(x => new SelectListItem { Value = x.Key.ToString(), Text = x.Value }).ToList();
In your View :
#Html.DropDownListFor(m => m.Status, Model.ListData));
When you post the data, the model property Status would be populated with the selected Enum value.
Hope it helps.

Iterating though items of Model in MVC 4

I would like to iterate though the item returned from the foreach(var item in Model). Like for(int i=0; i <item.length; i++) and then subsequently write the individual html code for the items returned by the model.
Unfortunately, I cannot seem to be able to access item.length as the property doesn't exists. Would anyone know how I could iterate through each model item so that I could save up on writing a lot of unnecessary HTML?
So some more context in lieu of the question being unclear:
Let's say my model is
public class RegisterViewModel
{
public string Name { get; set; }
public string Email { get; set; }
public string Password { get; set; }
}
Now I use a DB call to access the model data and parse it to the view via
RegisterViewModel model= db.RegisterViewModel.Where(data => data.Name == User.Name);
Now I pass this model to the view via view(model)
Now to display data for all the students, I can use a
model IEnumerable<Project.Models.RegisterViewModel>
#foreach(item in Model) {
#for(int i=0; i<item.Length;i++) { \\this is where I would like to iterate though the item
if(item[i]!= null) // using a for loop would avoid me having to write this if condition multiple times.
<div>#item[i].value</div>
}
}
You cannot iterate over item because item is of type RegisterViewModel. It doesn't make sense to be doing this.
If you want to display the value you could directly use it like this:
#model IEnumerable<Project.Models.RegisterViewModel>
#foreach(item in Model) {
<div>#item.Value</div>
}
Also bear in mind that the following line of code in your controller is wrong:
RegisterViewModel model = db.RegisterViewModel.Where(data => data.Name == User.Name);
The .Where extension method returns an IEnumerable<T> so you'd rather have:
IEnumerable<RegisterViewModel> model = db.RegisterViewModel.Where(data => data.Name == User.Name);
return View(model);
or if you prefer:
var model = db.RegisterViewModel.Where(data => data.Name == User.Name);
return View(model);

Find out what fields are being updated

I'm using LINQ To SQL to update a user address.
I'm trying to track what fields were updated.
The GetChangeSet() method just tells me I'm updating an entity, but doesn't tell me what fields.
What else do I need?
var item = context.Dc.Ecs_TblUserAddresses.Single(a => a.ID == updatedAddress.AddressId);
//ChangeSet tracking
item.Address1 = updatedAddress.AddressLine1;
item.Address2 = updatedAddress.AddressLine2;
item.Address3 = updatedAddress.AddressLine3;
item.City = updatedAddress.City;
item.StateID = updatedAddress.StateId;
item.Zip = updatedAddress.Zip;
item.Zip4 = updatedAddress.Zip4;
item.LastChangeUserID = request.UserMakingRequest;
item.LastChangeDateTime = DateTime.UtcNow;
ChangeSet set = context.Dc.GetChangeSet();
foreach (var update in set.Updates)
{
if (update is EberlDataContext.EberlsDC.Entities.Ecs_TblUserAddress)
{
}
}
Use ITable.GetModifiedMembers. It returns an array of ModifiedMemberInfo objects, one for each modified property on the entity. ModifiedMemberInfo contains a CurrentValue and OriginalValue, showing you exactly what has changed. It's a very handy LINQ to SQL feature.
Example:
ModifiedMemberInfo[] modifiedMembers = context.YourTable.GetModifiedMembers(yourEntityObject);
foreach (ModifiedMemberInfo mmi in modifiedMembers)
{
Console.WriteLine(string.Format("{0} --> {1}", mmi.OriginalValue, mmi.CurrentValue));
}
You can detect Updates by observing notifications of changes. Notifications are provided through the PropertyChanging or PropertyChanged events in property setters.
E.g. you can extend your generated Ecs_TblUserAddresses class like this:
public partial class Ecs_TblUserAddresses
{
partial void OnCreated()
{
this.PropertyChanged += new PropertyChangedEventHandler(User_PropertyChanged);
}
protected void User_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
string propertyName = e.PropertyName;
// do what you want
}
}
Alternatively, if you want to track a special property changing, you could use one of those OnPropertyNameChanging partial methods, e.g. (for City in your example):
partial void OnCityChanging(string value)
{
// value parameter holds a new value
}