I want to create an attribute to use with my viewmodel. I want to display different textstrings depending on a third value.
I would like to do something like this...
[DisplayIf("IsPropertyValid", true, Name="value 1")]
[DisplayIf("IsPropertyValid", false, Name="value 2")]
public string MyProperty { get; set; }
public bool IsPropertyValid { get; set; }
Depending on whether my value IsPropertyValid is true or not I want to show one or the other. Ie. When property IspPropertyValid equals true "value 1" will be the displaytext and if not it will be "value 2".
Is this possible with ASPNET.MVC attributes? Or even better... a combinated one like....
[DisplayIf("IsPropertyValid", new {"value 1", "value 2"})].
public string MyProperty { get; set; }
public bool IsPropertyValid { get; set; }
Then the attribute checks the value of IsPropertyValid and makes sure the value displayed is "value 1" or "value 2".
Here's an example of how to go about this.
What we'll do is create a simple class called Person and display some basic information about them.
A Person has two properties
Name
IsActive
The IsActive property is a bool value and will be the property used to determine what the user's name is displayed as.
Ultimately what we'll do is apply a new attribute called DisplayIf to the Name property. It looks like this:
[DisplayIf("IsActive", "This value is true.", "This value is false.")]
First, let's create our model. Create a class called Person and put it into a Models folder.
Models/Person.cs
public class Person
{
[DisplayIf("IsActive", "This value is true.", "This value is false.")]
public string Name { get; set; }
public bool IsActive { get; set; }
}
Create a folder called Attributes and then put the following class in it:
Attributes/DisplayIfAttribute.cs
public class DisplayIfAttribute : Attribute
{
private string _propertyName;
private string _trueValue;
private string _falseValue;
public string PropertyName
{
get { return _propertyName; }
}
public string TrueValue
{
get { return _trueValue; }
}
public string FalseValue
{
get { return _falseValue; }
}
public DisplayIfAttribute(string propertyName, string trueValue, string falseValue)
{
_propertyName = propertyName;
_trueValue = trueValue;
_falseValue = falseValue;
}
}
Let's create a simple controller and action. We'll use the common /Home/Index.
Controllers/HomeController.cs
public class HomeController : Controller
{
public ActionResult Index()
{
HomeIndexViewModel viewModel = new HomeIndexViewModel();
Person male = new Person() { Name = "Bob Smith", IsActive = true };
Person female = new Person() { Name = "Generic Jane", IsActive = false };
Person[] persons = {male, female};
viewModel.Persons = persons;
return View(viewModel);
}
}
Create a new folder called ViewModels and create a HomeViewModels.cs class.
ViewModels/HomeViewModels.cs
public class HomeIndexViewModel
{
public IEnumerable<Person> Persons { get; set; }
}
Our Index view is very simple.
Views/Home/Index.cshtml
#model HomeIndexViewModel
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<div>
#Html.DisplayForModel()
</div>
DisplayForModel will work when you create this display template:
Views/Home/DisplayTemplates/HomeIndexViewModel.cshtml
#model HomeIndexViewModel
#Html.DisplayFor(m => m.Persons)
DisplayFor -> Persons will work when you create this display template:
Views/Shared/DisplayTemplates/Person.cshtml
#model Person
#foreach (var prop in ViewData.ModelMetadata.Properties)
{
if (prop.HasDisplayIfAttribute())
{
<p>#Html.DisplayIfFor(x => prop)</p>
}
else
{
<p>#Html.DisplayFor(x => prop.Model)</p>
}
}
But what are these methods in this display template? Create a new folder called Extensions and add the following classes:
Extensions/ModelMetaDataExtensions.cs
public static class ModelMetaDataExtensions
{
public static bool HasDisplayIfAttribute(this ModelMetadata data)
{
var containerType = data.ContainerType;
var containerProperties = containerType.GetProperties();
var thisProperty = containerProperties.SingleOrDefault(x => x.Name == data.PropertyName);
var propertyAttributes = thisProperty.GetCustomAttributes(false);
var displayIfAttribute = propertyAttributes.FirstOrDefault(x => x is DisplayIfAttribute);
return displayIfAttribute != null;
}
}
Extensions/HtmlHelperExtensions.cs
public static class HtmlHelperExtensions
{
public static IHtmlString DisplayIfFor<TModel, TProperty>
(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression)
where TProperty : ModelMetadata
{
string returnValue = string.Empty;
var modelMetaData = expression.Compile().Invoke(helper.ViewData.Model);
var containerType = typeof(TModel);
var containerProperties = containerType.GetProperties();
var propertyInfo = containerProperties
.SingleOrDefault(x => x.Name == modelMetaData.PropertyName);
var attribute = propertyInfo.GetCustomAttributes(false)
.SingleOrDefault(x => x is DisplayIfAttribute) as DisplayIfAttribute;
var conditionalTarget = attribute.PropertyName;
var conditionalTargetValue = (bool)containerType
.GetProperty(conditionalTarget).GetValue(helper.ViewData.Model);
if (conditionalTargetValue)
{
returnValue = attribute.TrueValue;
}
else
{
returnValue = attribute.FalseValue;
}
return MvcHtmlString.Create(returnValue);
}
}
The final output:
Related
I have the following two classes where a user sets preferences. The user may not have any preferences, only likes, only dislikes, or both. I slimmed down the User model for simplicity in this example.
[BsonIgnoreExtraElements]
public class User : Base
{
public string FirstName { get; set; }
public string LastName { get; set; }
[BsonIgnoreIfNull]
public UserPreferences Preferences { get; set; }
}
[BsonIgnoreExtraElements]
public class UserPreferences
{
[BsonIgnoreIfNull]
public List<string> Likes { get; set; }
[BsonIgnoreIfNull]
public List<string> Dislikes { get; set; }
}
I have a helper function which uses reflection to construct an UpdateBuilder. When given a user object it sets a value for non-null fields since I don't want to specifically write out which fields have been updated on a call. However the helper function fails in my current situation.
public override User Update(User model)
{
var builder = Builders<User>.Update.Set(x => x.Id, model.Id);
foreach(PropertyInfo prop in model.GetType().GetProperties())
{
var value = model.GetType().GetProperty(prop.Name).GetValue(model, null);
if ((prop.Name != "Id") & (value != null))
{
builder = builder.Set(prop.Name, value);
}
}
var filter = Builders<User>.Filter;
var filter_def = filter.Eq(x => x.Id, model.Id);
Connection.Update(filter_def, builder);
return model;
}
Problem: When supplying Preferences with only Likes or only Dislikes, it will make the other property null in MongoDB.
Desired Result: I want MongoDB to ignore either the Likes or Dislikes property if the list is null like it does for other properties in my code.
I think the best way is to unset the field if its value is null
public override User Update(User model)
{
var builder = Builders<User>.Update.Set(x => x.Id, model.Id);
foreach(PropertyInfo prop in model.GetType().GetProperties())
{
var value = model.GetType().GetProperty(prop.Name).GetValue(model, null);
if (prop.Name != "Id")
{
if(value != null)
{
builder = builder.Set(prop.Name, value);
}
else
{
builder = builder.Unset(prop.Name);
}
}
}
var filter = Builders<User>.Filter;
var filter_def = filter.Eq(x => x.Id, model.Id);
Connection.Update(filter_def, builder);
return model;
}
I hope this will solve your issue
I have a list with Strings of Display names of Model:
public class TVSystemViewData : BaseViewData
{
[Display(Name = "AccountId", Description = "")]
public String AccountId { get; set; }
[Display(Name = "AllocatedManagedMemory", Description = "")]
public String AllocatedManagedMemory { get; set; }
[Display(Name = "AllocatedPhysicalMemory", Description = "")]
public String AllocatedPhysicalMemory { get; set; }
[Display(Name = "AudioMute", Description = "")]
public String AudioMute { get; set; }
}
I need to set the properties with a foreach loop to add the values to my Model:
This is how get values from POST the application
var model.AccountId = shell.getParameter("AccountId")
var model.AllocatedManagedMemory = shell.getParameter("AllocatedManagedMemory");
The shell.GetParameter get the value from a POST.
this is how i want:
I have a a Method to get all Display attr
public List<String> GetTVSystemProperties()
{
return typeof(TVSystemViewData)
.GetProperties()
.SelectMany(x => x.GetCustomAttributes(typeof(DisplayAttribute), true) //select many because can have multiple attributes
.Select(e => ((DisplayAttribute)e))) //change type from generic attribute to DisplayAttribute
.Where(x => x != null).Select(x => x.Name) //select not null and take only name
.ToList();
}
My collection is a list of Strings
ex: collection {"AccountId","AllocatedManagedMemory"...}
My model is TVSystemViewData
foreach (item in collection)
{
if(item == modelProperty name){
// i don know how
model.property = shell.getParameter(item)
}
}
[UPDATED]
I am using this:
foreach (var property in UtilsHandler.getConfigAsList("sysDataSource"))
{
//Set Values to Model
try
{
model.GetType().GetProperty(property).SetValue(model, shell.getParameter(property), null);
}
catch (Exception)
{
Have issue with data types
}
}
I have issues with data types.
But i use one foreach loop.
Still looking for a best method
you need to make the class inherit from IEnumerator, or add a GetEnumerator method yourself.
var model = new TVSystemViewData();
foreach(var item in model)
{
item.AccountId = shell.getParameter("AccountId");
//item.AllocatedManagedMemory ...
}
Please refer to this post for more information : How to make the class as an IEnumerable in C#?
Check this article out: https://support.microsoft.com/en-us/help/322022/how-to-make-a-visual-c-class-usable-in-a-foreach-statement
//EDIT. Forgot this part :
List<TVSystemViewData> model;
[Display(Name = "AccountId", Description = "")]
public String AccountId { get; set; }
[Display(Name = "AllocatedManagedMemory", Description = "")]
public String AllocatedManagedMemory { get; set; }
[Display(Name = "AllocatedPhysicalMemory", Description = "")]
public String AllocatedPhysicalMemory { get; set; }
[Display(Name = "AudioMute", Description = "")]
public String AudioMute { get; set; }
public IEnumerator<TVSystemViewData> GetEnumerator()
{
foreach (var item in model)
{
yield return item;
}
}
EDIT According to your update question: I don't know if this is the way to go but it should work.
var model = new TVSystemViewData();
PropertyInfo[] properties = typeof(TVSystemViewData).GetProperties();
List<string> items = new List<string> { "AccountId", "AllocatedManagedMemory" }; //your collection of strings
foreach (var item in items)
{
foreach (var property in properties)
{
if (item == property.Name)
{
property.SetValue(model, shell.getParameter(item));
}
}
}
i have this code
public class ParameterOrderInFunction : Attribute
{
public int ParameterOrder { get; set; }
public ParameterOrderInFunction(int parameterOrder)
{
this.ParameterOrder = parameterOrder;
}
}
public interface IGetKeyParameters
{
}
public class Person: IGetKeyParameters
{
[ParameterOrderInFunction(4)]
public string Age { get; set; }
public string Name { get; set; }
[ParameterOrderInFunction(3)]
public string Address { get; set; }
[ParameterOrderInFunction(2)]
public string Language { get; set; }
[ParameterOrderInFunction(1)]
public string City { get; set; }
public string Country { get; set; }
}
class Program
{
static void Main(string[] args)
{
Person person = new Person();
person.Address = "my address";
person.Age = "32";
person.City = "my city";
person.Country = "my country";
Test t = new Test();
string result = t.GetParameter(person);
//string result = person.GetParameter();
Console.ReadKey();
}
}
public class Test
{
public string GetParameter(IGetKeyParameters obj)
{
string[] objectProperties = obj.GetType()
.GetProperties()
.Where(p => Attribute.IsDefined(p, typeof(ParameterOrderInFunction)))
.Select(p => new
{
Attribute = (ParameterOrderInFunction)Attribute.GetCustomAttribute(p, typeof(ParameterOrderInFunction), true),
PropertyValue = p.GetValue(this) == null ? string.Empty : p.GetValue(this).ToString()
})
.OrderBy(p => p.Attribute.ParameterOrder)
.Select(p => p.PropertyValue)
.ToArray();
string keyParameters = string.Join(string.Empty, objectProperties);
return keyParameters;
}
}
What i am trying to do is to get properties values as one string with some order .
it work fine if i put the function GetParameter inside the Person class.
however, i want to use the function GetParameter with other class as well,
so i create empty interface.
Now i want that every object that is of type IGetKeyParameters can use the function.
but i am getting exception in the line:
PropertyValue = p.GetValue(this) == null ? string.Empty : p.GetValue(this).ToString()
You should change loading properties from this (that doesn't have such properties) to parameter object:
PropertyValue = p.GetValue(obj) == null ? string.Empty : p.GetValue(obj).ToString()
You are passing the wrong reference as parameter to the method, you need to pass the object which you used to get the type and properties, so change:
p.GetValue(this) // this means pass current instance of containing class i.e. Test
to:
p.GetValue(obj)
Your statement p.GetValue(this) currenly means to pass the current instance of class Test as parameter which is i am pretty sure not what you want.
in your example code.
I have a view model that have a lot of properties, and I created a custom attribute for send the data to hubspot, because hubspot need a specific nomenclature, then I need to create a method that have some king of iterator, that for every property that contain my custom attribute he put a specific output, here is the code:
public class CreateTrialUserHubspotViewModel {
[HubspotAttribute("firstname")]
[Display(Name = "First Name")]
[StringLength(50)]
[Required]
public string FirstName { get; set; }
[HubspotAttribute("lastname")]
[Display(Name = "Last Name")]
[StringLength(50)]
[Required]
public string LastName { get; set; }
[HubspotAttribute("jobtitle")]
[Display(Name = "Job title")]
[StringLength(50)]
[Required]
public string JobTitle { get; set; }
}
Now this is my custom attribute
[AttributeUsage(AttributeTargets.All)]
public class HubspotAttribute : System.Attribute {
public readonly string hubspotValue;
public HubspotAttribute(string value)
{
this.hubspotValue = value;
}
}
And then I need to create a method that take a viewmodel object and create my output, I need some suggest about how to do that, will be something like this:
private static RowValidation ValidateRowWithManifest<T>(CreateTrialUserHubspotViewModel trialUser) {
RowValidation validation = new RowValidation();
FieldInfo[] fields = typeof(T).GetPropertiesOfSomeWay;
foreach (DataType field in fields) {
output+=whatINeed
}
return validation;
}
}
The needed output will be like: [firstname:"pepe", lastname="perez", jobtitle"none"]. just calling that method will return all the data I need.
public string GetString<T>(T #object)
{
var output = new StringBuilder();
var type = typeof(T);
var properties = type.GetProperties();
foreach (var property in properties)
{
var attributes = property.GetCustomAttributes(typeof(HubspotAttribute), true);
if (attributes.Length == 0)
continue;
var name = ((HubspotAttribute)attributes[0]).hubspotValue;
var value = property.GetValue(#object) ?? "none";
output.AppendFormat("{0}:\"{1}\",", name, value);
}
var fields = output.ToString().TrimEnd(',');
return string.Format("[{0}]", fields);
}
If you are looking for something that will concatenate properties into a string that looks like a JSON string (and that would be a better way to handle it), you can use something like the following:
private static string CreateOutput(CreateTrialUserHubspotViewModel trialUser)
{
var properties = trialUser.GetType().GetProperties().Where(x => Attribute.IsDefined(x, typeof(HubspotAttribute))).ToList();
var values = properties.Select(x =>
{
var att = x.GetCustomAttribute(typeof(HubspotAttribute));
var key = ((HubspotAttribute)att).hubspotValue;
var val = x.GetValue(trialUser);
return $"{key}:{val}";
});
var sb = new StringBuilder();
values.ToList().ForEach(v =>
{
sb.Append(v);
if (values.Last() != v) sb.Append(',');
});
return sb.ToString();
}
I have this Converter class.
public class StatsConverter : TypeConverter<Tuple<Player,Stats>, StatsModel>
, ITypeConverter<Stats, StatsModel>
{
protected override StatsModel ConvertCore(Tuple<Player, Stats> source)
{
var pm = new StatsModel
{
Id = source.Item2.Id,
PlayerId = source.Item1.Id,
DisplayName = source.Item2.DisplayName,
};
return pm;
}
}
How do I return a IEnumerable<StatsModel> in the following scenario where I have List ---- player.PlayerStats that needs to be mapped?
[HttpGet("{id:int}/PlayerStats", RouteName = "GetPlayerStats")]
public IEnumerable<StatsModel> GetPlayerStats(int id)
{
var user = this._manager.GetPlayerById(id);
// I can retrieve a "List" of player.PlayerStats.
//But how do I map and return it?
return this._mapper.Map<IEnumerable<StatsModel>>(????)
}
When I do this
return this._mapper.Map<IEnumerable<StatsModel>>(player.PlayerStats);
I get this error:
Change the type converter source type, or redirect the source value supplied to the value resolver using FromMember.
public class StatsConverter : ITypeConverter<Tuple<Player, Stats>, StatsModel>
{
public StatsModel Convert(Tuple<Player, Stats> source)
{
return new StatsModel
{
Id = source.Item2.Id,
PlayerId = source.Item1.Id,
DisplayName = source.Item2.DisplayName,
};
}
}
First ensure your converter is specified.
Mapper.CreateMap<Tuple<Player,Stats>, StatsModel>
.ConvertUsing<StatsConverter>();
I'm going to assume from your question that the Player class is structured (correct me if I'm wrong):
public class Player
{
public int Id { get; set; }
public IEnumerable<Stats> PlayerStats { get; set; }
}
Inside your method you will need to get the player and stats grouped together.
var user = _manager.GetPlayerById(id);
// Expand the player stats
var playerStats = user.PlayerStats
.Select(stats => Tuple.Create(user, stats));
Mapper.Map<IEnumerable<Tuple<Player,Stats>>,
IEnumerable<StatsModel>>(playerStats);