I want to inject a UIHint attribute into a model object on the fly. I have been using the ICustomTypeDescriptor to create a class that will inject a UIHint into an instance of an object:
public sealed class UIHintDescriptionProvider : TypeDescriptionProvider
{
private string PropertyName;
private string HintValue;
public UIHintDescriptionProvider(TypeDescriptionProvider parent, string propertyName, string hintValue)
: base(parent)
{
this.PropertyName = propertyName;
this.HintValue = hintValue;
}
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
{
return new UIHintDescriptor(base.GetTypeDescriptor(objectType, instance), this.PropertyName, this.HintValue);
}
}
public sealed class UIHintDescriptor : CustomTypeDescriptor
{
private string PropertyName;
private string HintValue;
internal UIHintDescriptor(ICustomTypeDescriptor parent, string propertyName, string hintValue)
: base(parent)
{
this.PropertyName = propertyName;
this.HintValue = hintValue;
}
public override PropertyDescriptorCollection GetProperties()
{
// Enumerate the original set of properties and create our new set with it
PropertyDescriptorCollection originalProperties = base.GetProperties();
List<PropertyDescriptor> newProperties = new List<PropertyDescriptor>();
foreach (PropertyDescriptor pd in originalProperties)
{
if (pd.Name == this.PropertyName)
{
Attribute attr = new UIHintAttribute(this.HintValue);
var newProp = TypeDescriptor.CreateProperty(typeof(object), pd, attr);
newProperties.Add(newProp);
}
else
{
newProperties.Add(pd);
}
}
// Finally return the list
return new PropertyDescriptorCollection(newProperties.ToArray(), true);
}
}
I then set this in my controller using:
UIHintDescriptionProvider provider =
new UIHintDescriptionProvider(TypeDescriptor.GetProvider(typeof(PageContentItem)), "Text",
"wysiwyg");
TypeDescriptor.AddProvider(provider, item);
Inspection in the controller of this object using the functions of TypeDescriptor indicate that this attribute has indeed been set however it does not appear in my view at all. Stepping through the MVC3 source shows all the other attributes but not the one I have just set.
Does MVC3 do any caching of object type descriptions in the background that could account for that fact?
Any other suggestions for injecting an attribute into an object instance at runtime?
This is probably because of 'timing'.
Try using a custom ModelMetadataProvider to programmatically set model property attributes like 'UIHint' or 'DisplayName' or ...
Have a look here.
Related
I have 4 classes namely ClassA, ClassADto, ClassAA(inner class to ClassA) and the final Result class.
ClassAA
{
public int HouseNumber{get;set;}
public string StreetName{get;set;}
public string State{get;set;}
}
ClassA
{
public int Age{get;set;}
public string Name{get;set;}
public ClassAA AObj[get;set;}
}
ClassADto
{
public int Age{get;set;}
public string Name{get;set;}
}
class Result
{
public string StreetName{get;set;}
public int TotalCount{get;set;}
public int TodaysDate{get;set;}
public List<ClassADto> AObjectsList{get;set;}
}
Now my aim is map the 'Result' class with the List of ClassA object to fill it the property 'AObjectsList' as below:
Result data= map mapper.map>(obj);
Also at the same time in automapper i want to use custom function either using 'Resolve' or 'AfterMap' to set properties like 'TodaysDate' to current datetime of system and property 'TotalCount' by counting the number of data.
I tried in many ways using 'CreateMap' and also used 'ForMembers' as from 'classAA' we only need the 'StreetName' but it didn't work. Need some help please.
One time typing approach ;)
public static Result ToResult(this List<ClassA> users)
{
return new Result
{
TotalCount = users.Count,
TodaysDate = DateTime.Today,
AObjectsList = users
.Select(user => new ClassADto
{
Name = user.Name,
Age = user.Age
})
.ToList()
};
}
// Usage
var users = new List<ClassA> { new ClassA(), new ClassA() };
var result = users.ToResult();
I have a model such as
public class MyModel
{
public MyObject myObject {get;set;}
}
public class MyObject
{
public string FirstName {get;set;}
public string LastName {get;set;}
}
With out using a custom model binder everything works great. I am trying to implement a model binder and not getting anywhere -- the resources that I have come from are
https://www.youtube.com/watch?v=qDRORgoZxZU (returns null model to the controller)
http://intellitect.com/custom-model-binding-in-asp-net-core-1-0/ (controller dies on the constructor)
http://hotzblog.com/asp-net-vnext-defaultmodelbinder-and-automatic-viewmodel-string-trim/ (can not even find MutableObjectModelBinder in the .net-core namespace)
Ideally what I want is to track which properties where set by the ModelBinder.
public class MyObject
{
public string FirstName {get;set;}
public string LastName {get;set;}
public List<String> ModifiedProperties {get;set;}
}
when the object is created by the ModelBinder for each property that is being set it adds it to the ModifiedProperties list.
This is solution. You need to implement IModelBinderProvider and IModelBinder
public class EntityFrameworkModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
//We only want to invoke the CustomeBinder on IBaseEntity classes
if (context.Metadata.ContainerType != null && context.Metadata.ContainerType.GetInterfaces().Contains(typeof(SurgeOne.Core.IBaseEntity)))
{
//We only create the custom binder on value types. E.g. string, guid, etc
if (context.Metadata.ModelType.GetTypeInfo().IsValueType ||
context.Metadata.ModelType == typeof(System.String))
{
return new EntityFrameworkModelBinder();
}
}
return null;
}
}
And IModelBinder
public class EntityFrameworkModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
//Get the value
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult == ValueProviderResult.None)
{
// no entry
return Task.CompletedTask;
}
//Set the value -- not sure what this does
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
//Set the value -- this has to match the property type.
System.ComponentModel.TypeConverter typeConverter = System.ComponentModel.TypeDescriptor.GetConverter(bindingContext.ModelType);
object propValue = typeConverter.ConvertFromString(valueProviderResult.FirstValue);
bindingContext.Result = ModelBindingResult.Success(propValue);
//Code to track changes.
return Task.CompletedTask;
} //BindModelAsync
}
I need to access a property inside a custom DataAnnotation attribute. How can I access this attribute in order to set the response value? The attribute is added to the model property.
public class BirthDateAttribute : ValidationAttribute
{
public string ErrorCode { get; set; }
....
}
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
List<Errors> errors = new List<Errors>();
// Set error message and errorCode
foreach (var key in keys)
{
if (!actionContext.ModelState.IsValidField(key))
{
error.Add(new HttpResponseError
{
Code = ???????????,
Message = actionContext.ModelState[key].Errors.FirstOrDefault().ErrorMessage
});
}
}
// Return to client
actionContext.Response = actionContext.Request.CreateResponse(
HttpStatusCode.BadRequest, errors);
}
}
}
Assuming that the custom attribute is applied to the controller, you can try following in the OnActionExecuting event. This similar thing works with MVC controller but should work with API controller too.
var att = actionContext.ControllerContext.GetType().GetCustomAttributes(typeof(BirthDateAttribute), false)[0] as BirthDateAttribute;
string errorCode = att.ErrorCode;
As mentioned by OP, if this is on a class (Model), it should be pretty starightforward because the type is already known. Replace the Model class.
var att = <<ModalClass>>.GetCustomAttributes(typeof(BirthDateAttribute), false)[0] as BirthDateAttribute;
string errorCode = att.ErrorCode;
Here is my development requirement,
My label values are stored in the database, and I still want to use the data annotation in a declarative way, this is to make my model more readable.
And here is my approach,
I decided to write custom DisplayNameAttribute, where the default value provided by my model will be overwritten by the value retrieved from the database.
Here is the property defined in the model,
[CustomDisplay(Name: "First Name")]
[CustomRequired(ErrorMessage: "{0} is required")]
public String FirstName { get; set; }
Here is the custom display name attribute class,
public class CustomDisplayAttribute : DisplayNameAttribute
{
private string _defaultName;
private string _displayName;
public CustomDisplayAttribute(string Name)
{
_defaultName = Name;
}
public override string DisplayName
{
get
{
if (String.IsNullOrEmpty(_displayName))
{
_displayName = DAO.RetrieveValue(**ModelName**, _defaultName);
}
return _displayName;
}
}
}
Now, you can see in the above code, ModelName is something I need, but I don't have!!
While debugging, I dig into ModelMetadataProviders.Current and can see the availability of the current model in action. But, as it is part of non-public static members I am unable to access it through my code.
I have written the below method to retrieve the model name through reflection,
private static string GetModelName()
{
var modelName = String.Empty;
FieldInfo info = typeof(CachedAssociatedMetadataProvider<CachedDataAnnotationsModelMetadata>)
.GetField("_typeIds", BindingFlags.NonPublic | BindingFlags.Static);
var types = (ConcurrentDictionary<Type, string>)info.GetValue(null);
modelName = types.FirstOrDefault().Key.Name;
return modelName;
}
But the problem is, the types collection provides me entries for all the models (visited at least once by the user). And there is no clue to know, which is currently in action!!
IMHO Attributes should not be used to make database calls. Attributes should be used to add metadata to Classes/Properties etc...
So If you're willing to change your code to be more like the Microsoft architecture for MVC then you'd have your custom Attribute and a custom ModelMetadataProvider:
public class CustomDisplayAttribute : Attribute
{
public CustomDisplayAttribute(string name)
{
Name = name;
}
public string Name { get; private set; }
}
Then a new ModelMetadataProvider:
public class DatabaseModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
public DatabaseModelMetadataProvider()
{
}
protected override ModelMetadata CreateMetadata(
IEnumerable<Attribute> attributes,
Type containerType,
Func<object> modelAccessor,
Type modelType,
string propertyName)
{
var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
var displayAttribute = containerType == null
? null as CustomDisplayAttribute
: containerType.GetProperty(propertyName)
.GetCustomAttributes(false)
.OfType<CustomDisplayAttribute>()
.FirstOrDefault();
if (displayAttribute != null)
{
var displayValue = DAO.RetrieveValue(containerType.ToString(), displayAttribute.Name)
metadata.DisplayName = displayValue;
}
return metadata;
}
}
Where
public class MyViewModel
{
public MyPropertyType PropertyName { get; set; }
}
containerType = MyViewModel
modelType = MyPropertyType
propertyName = PropertyName
Then register the provider (global.asax or whatever):
ModelMetadataProviders.Current = new LocalizedModelMetadataProvider();
Also you can take a look at the ModelMetadata it has a few other things you might want to change in the future.
I want to scan a type for it's properties and the annotated attributes and return an object with the following structure
public class PropertyContext
{
public object PropertyValue { get; set; }
public object SourceType { get; set; }
public Attribute Annotation { get; set; }
}
I have this query
var query = from property in _target.GetType().GetProperties()
from attribute in Attribute.GetCustomAttributes(property, true)
select new PropertyContext
{
Annotation = attribute,
SourceType = _target,
};
This is executed deferred so i only generate the PropertyContext while the calling method needs them.
Now i want to fill the PropertyValue property of the PropertyContext object.
To get the value of the property i have have a call to an other component like this
_propertyValueAccessor.GetValue(_target, property)
My question is, how i can modify the query in a way that
*
the value is only read once
but only if a PropertyContext is created
How about:
var query = from property in _target.GetType().GetProperties()
let attributes = Attribute.GetCustomAttributes(property, true)
where attributes.Any()
let val = _propertyValueAccessor.GetValue(_target, property)
from attribute in attributes
select new PropertyContext
{
PropertyValue = val,
Annotation = attribute,
SourceType = _target,
};