Custom Data Annotations ASP.NET MVC C# - c#

I have the follwing question about MVC 2 with C#.
Here is my Model:
public class Pmjob
{
[Tooltext="Hier soll der Name eingegeben werden"]
[DisplayName("Type")]
public int Name { get; set; }
}
Now I want to reach the Tooltext item in my view, e. g.:
#Html.ToolTextFor(Model => Model.Pmjob.Name)
or in the BL:
if ( Model.Pmjob.Name.Tooltext == "") {
}
Is this possible?

Create an abstract class MetaDataAttribute :
public abstract class MetadataAttribute : Attribute
{
/// <summary>
/// Method for processing custom attribute data.
/// </summary>
/// <param name="modelMetaData">A ModelMetaData instance.</param>
public abstract void Process(ModelMetadata modelMetaData);
}
Make your attribute inherit from MetaDataAttribute :
public class ToolTextAttribute : MetadataAttribute
{
public string Text { get; set; }
public TooltextAttribute(string text)
{
this.Text = new text;
}
public override void Process(ModelMetadata modelMetaData)
{
modelMetaData.AdditionalValues.Add("ToolText", this.Text);
}
}
Create the custom MetaDataProvider :
public class CustomModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
protected override ModelMetadata CreateMetadata(
IEnumerable<Attribute> attributes,
Type containerType,
Func<object> modelAccessor,
Type modelType,
string propertyName)
{
var modelMetadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
attributes.OfType<MetadataAttribute>().ToList().ForEach(x => x.Process(modelMetadata));
return modelMetadata;
}
}
And replace the default one (global.asax.cs) :
protected void Application_Start()
{
// snipped
ModelMetadataProviders.Current = new CustomModelMetadataProvider();
}
Finally, you can access it in your view (or in a Html Helper ) :
(string)ViewData.ModelMetadata.AdditionalValues.Where(x => x.Key == "ToolText").SingleOrDefault()
Source :
http://weblogs.asp.net/seanmcalinden/archive/2010/06/11/custom-asp-net-mvc-2-modelmetadataprovider-for-using-custom-view-model-attributes.aspx
http://weblogs.asp.net/seanmcalinden/archive/2010/06/12/asp-net-mvc-2-auto-complete-textbox-custom-view-model-attribute-amp-editortemplate.aspx

Related

Dependency Injection for custom validation attributes

I created a custom validation attribute that I want to use for my API controller DTOs. This attribute needs values from the configured options, that's why I'm injecting them in the constructor, so that I can use the options service later on in the IsValid and FormatErrorMessage method.
internal class MyValidationAttribute : ValidationAttribute
{
private readonly IOptionsMonitor<MyOptions> myOptionsMonitor;
public MyValidationAttribute(IOptionsMonitor<MyOptions> myOptionsMonitor)
{
this.myOptionsMonitor = myOptionsMonitor;
}
public override bool IsValid(object value)
{
// ... use myOptionsMonitor here ...
return false;
}
public override string FormatErrorMessage(string name)
{
// ... use myOptionsMonitor here ...
return string.Empty;
}
}
Unfortunately when I want to use this as an attribute in my DTO
internal class MyDTO
{
[MyValidationAttribute]
public string Foo { get; set; }
}
I get the error message
There is no argument given that corresponds to the required formal
parameter 'myOptionsMonitor' of
'MyValidationAttribute.MyValidationAttribute(IOptionsMonitor)'
Is there a way I can use dependency injection for validation attributes? I know that I can use the ValidationContext like so
internal class MyValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
IOptionsMonitor<MyOptions> myOptionsMonitor = validationContext.GetService<IOptionsMonitor<MyOptions>>();
// ...
return ValidationResult.Success;
}
return new ValidationResult("Something failed");
}
}
But I want to use the FormatErrorMessage method from the base class and this has no access to the options service.
My current solution
For now, this is the code I'm using
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
internal class CustomValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
IOptionsMonitor<MyOptions> myOptionsMonitor = validationContext.GetService<IOptionsMonitor<MyOptions>>();
Dictionary<string, string> myMap = myOptionsMonitor.CurrentValue.MyMap;
string key = value.ToString() ?? string.Empty;
if (myMap.ContainsKey(key))
return ValidationResult.Success;
string[] formattedKeys = myMap.Keys.Select(key => $"'{key}'").ToArray();
string keysText = string.Join(" / ", formattedKeys);
string errorMessage = $"Invalid value. Valid ones are {keysText}";
return new ValidationResult(errorMessage);
}
}
Attributes are not designed for this purpose. But you can use action filters instead.
Let`s make your attribute as simple as it can be, we don't need any validation logic there.
[AttributeUsage(AttributeTargets.Property)]
public class CustomValidationAttribute : Attribute
{ }
For my example I created service that we are going to inject
public class SomeService
{
public bool IsValid(string str)
{
return str == "Valid";
}
}
and a class that we are going to validate
public class ClassToValidate
{
[CustomValidation]
public string ValidStr { get; set; } = "Valid";
[CustomValidation]
public string InvalidStr { get; set; } = "Invalid";
}
Now we can finally create action filter to validate our properties. In the snippet below, we hook into ASP.NET Core pipeline to execute code just before our controller action executes. Here I get action arguments and try to find CustomValidationAttribute on any property. If it is there, grab the value from the property, cast to type (I simply invoke .ToString()) and pass to your service. Based on value that is returned from service, we continue execution or add error to ModelState dictionary.
public class CustomValidationActionFilter : ActionFilterAttribute
{
private readonly SomeService someService;
public CustomValidationActionFilter(SomeService someService)
{
this.someService = someService;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
var actionArguments = context.ActionArguments;
foreach (var actionArgument in actionArguments)
{
var propertiesWithAttributes = actionArgument.Value
.GetType()
.GetProperties()
.Where(x => x.GetCustomAttributes(true).Any(y => y.GetType() == typeof(CustomValidationAttribute)))
.ToList();
foreach (var property in propertiesWithAttributes)
{
var value = property.GetValue(actionArgument.Value).ToString();
if (someService.IsValid(value))
continue;
else
context.ModelState.AddModelError(property.Name, "ModelState is invalid!!!");
}
}
base.OnActionExecuting(context);
}
}
Don't forget to add your filter to the pipeline in Startup.cs!
services.AddMvc(x =>
{
x.Filters.Add(typeof(CustomValidationActionFilter));
});
Update:
If you strictly want to use dependency injection inside attribute, you could use service locator anti-pattern. For that we need to emulate DependencyResolver.Current from ASP.NET MVC
public class CustomValidationAttribute : ValidationAttribute
{
private IServiceProvider serviceProvider;
public CustomValidationAttribute()
{
serviceProvider = AppDependencyResolver.Current.GetService<IServiceProvider>();
}
public override bool IsValid(object value)
{
// scope is required for scoped services
using (var scope = serviceProvider.CreateScope())
{
var service = scope.ServiceProvider.GetService<SomeService>();
return base.IsValid(value);
}
}
}
public class AppDependencyResolver
{
private static AppDependencyResolver _resolver;
public static AppDependencyResolver Current
{
get
{
if (_resolver == null)
throw new Exception("AppDependencyResolver not initialized. You should initialize it in Startup class");
return _resolver;
}
}
public static void Init(IServiceProvider services)
{
_resolver = new AppDependencyResolver(services);
}
private readonly IServiceProvider _serviceProvider;
public object GetService(Type serviceType)
{
return _serviceProvider.GetService(serviceType);
}
public T GetService<T>()
{
return (T)_serviceProvider.GetService(typeof(T));
}
private AppDependencyResolver(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
}
It should be initialized in Startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
AppDependencyResolver.Init(app.ApplicationServices);
// other code
}

Compare types in base class

I have a Part base class
class Part {
public PartType Type { get; set; }
}
with many implementations.
class Wire : Part { }
I have a TreeView in my program. And when I click on an element of it, I want a list to be filled with all the Parts of the Type I clicked in the TreeView.
When I have multiple lists open I want only those be loading the parts that have the same PartType as I clicked in the TreeView.
class BasePartListViewModel<T> : ListViewModel where T : Part {
protected override void OnTreeSelectionChanged(PartType type)
if (type == PartType.Wire) {
//load the Wires from the DB and display them
}
else {
//ignore the changed type event
}
}
}
But since this is a base class for all Parts by using T I would like to replace
if (_type == PartTypeEnum.Wire)
with something like
if (_type == T.Type)
but that does of course not work. How else?
Since the part type is a static information for a class type by design (am I right?), you could use an attribute to store it:
[AttributeUsage(AttributeTargets.Class)]
public class PartTypeAttribute : Attribute
{
public readonly PartType PartType;
public PartTypeAttribute(PartType partType)
{
PartType = partType;
}
}
Then apply it to the descendant classes:
[PartType(PartType.Wire)]
class Wire : Part
{
}
Then in the static constructor of the BasePartListViewModel class you can obtian the corresponding value:
class BasePartListViewModel<T> : ListViewModel
where T : Part
{
private static PartType PartTypeOfT;
static BasePartListViewModel()
{
var attr = typeof(T).GetCustomAttributes(typeof(PartTypeAttribute), true)
.FirstOrDefault() as PartTypeAttribute;
if (attr != null)
PartTypeOfT = attr.PartType;
}
protected override void OnTreeSelectionChanged(PartType type)
{
if (type == PartTypeOfT) {
....
}
}
}
If you do this.GetType() it will return as BasePartListViewModel`1[Wire]
You should not be ideally referring this in the base class.

Using Unity's dependency injection in (data) annotations

I am using Unity and have a Model tagged with data annotations:
public class SomeModel
{
[SlackDisplayName("ED0CAD76-263E-496F-ABB1-A4DFE6DEC5C2")]
public String SomeProperty { get; set; }
}
This SlackDisplayName property is a child class of DisplayName, which resolves a static Display Name for the property. I just wanted to make that dynamically by having this criteria fulfilled:
The use of this annotation is possible.
I can implement multilingual applications using that annotation.
Language templates get identified by a GUID
I must not pass a culture id to the annotation
So furthermore, my SlackDisplayName annotation looks like this:
/// <summary>
/// Annotation for non-fixed display names
/// </summary>
public class SlackDisplayNameAttribute : DisplayNameAttribute
{
/// <summary>
/// TODO
/// </summary>
/// <param name="identifierGUID"></param>
public SlackDisplayNameAttribute(String identifierGUID)
: this(Guid.Parse(identifierGUID))
{
}
/// <summary>
/// TODO
/// </summary>
/// <param name="identifier"></param>
public SlackDisplayNameAttribute(Guid identifier)
: base()
{
}
/// <summary>
/// The culture context to use.
/// </summary>
[Dependency]
public ICultureContext Context { get; set; }
/// <summary>
/// Gets the display name for the given GUID.
/// </summary>
public override string DisplayName
{
get
{
return "NOT_DEFINED";
//return Context.GetLanguageTemplate(new Guid());
}
}
}
And now the question is: How to get the ICultureContext from my Unity Container:
[Dependency]
public ICultureContext Context { get; set; }
It is registered, but I have no clue about how to get that property injected.
I have solved it myself!
First of all, you need the following Unity Extension and Strategy:
Info: Found here: UnityContainer.BuildUp() - Can I make it inject new instances into properties only if these are null?
public class RecursiveBuildUpContainerExtension : UnityContainerExtension {
protected override void Initialize(){
Context.Strategies.Add( new RecursiveBuildUpBuilderStrategy( Context.Container ), UnityBuildStage.PreCreation );
}
}
public class RecursiveBuildUpBuilderStrategy : BuilderStrategy {
readonly IUnityContainer container;
public RecursiveBuildUpBuilderStrategy( IUnityContainer container ) {
this.container = container;
}
public override void PreBuildUp( IBuilderContext context ) {
if( context.Existing == null ) return;
foreach( var prop in context.Existing.GetType( ).GetProperties( ) ) {
if( ContainsType<DependencyAttribute>( prop.GetCustomAttributes( true ) ) ) {
if( prop.GetValue( context.Existing, null ) == null ) {
var value = container.Resolve( prop.PropertyType );
prop.GetSetMethod( ).Invoke( context.Existing, new[] { value } );
}
else {
var value = container.BuildUp( prop.PropertyType, prop.GetValue( context.Existing, null ) );
prop.GetSetMethod( ).Invoke( context.Existing, new[] { value } );
}
}
}
foreach (var method in context.Existing.GetType().GetMethods() ){
if( ContainsType<InjectionMethodAttribute>( method.GetCustomAttributes( true ))){
var argsInfo = method.GetParameters( );
var args = new object[argsInfo.Length];
for( int i = 0; i < argsInfo.Length; i++ ) {
args[i] = container.Resolve( argsInfo[i].ParameterType );
}
method.Invoke( context.Existing, args );
}
}
context.BuildComplete = true;
}
private static bool ContainsType<T>( IEnumerable<object> objects ){
foreach (var o in objects){
if( o is T ) return true;
}
return false;
}
}
You need this, because it is responsible for injecting the properties on "BuildUp". Next to this, you need to register your extension
container.AddNewExtension<RecursiveBuildUpContainerExtension>();
Furthermore, you need to override the default DataAnnotationsModelMetadataProvider, because the default ModelMetaDataProvider does not use Unity to inject properties to annotations. To do this, implement this class:
public class DynamicModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
private IUnityContainer _context;
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
foreach (Attribute attribute in attributes)
_context.BuildUp(attribute);
return base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
}
public DynamicModelMetadataProvider(IUnityContainer context)
: base()
{
this._context = context;
}
}
After that, edit your bootstrapper and set the new ModelMetadataProvider, to make it clear to the MVC Framework that it has to use it:
ModelMetadataProviders.Current = new DynamicModelMetadataProvider(container);
Where container is your set up IUnityContainer. You should now have instances in your Annotations Instance when having set the [DependencyAttribute] and your Methods marked with the [InjectionMethod] should get called.
[Dependency]
public ICultureContext Context { get; set; }
Hope you could use this if you had a similar problem ;)

Apply Custom Validator by interface

Let's say I have an interface:
public interface ISomeInterface
{
bool SomeBool { get; set; }
string ValueIfSomeBool { get; set; }
}
And I have a number of classes that implement that. i.e.
public class ClassA : ISomeInterface
{
#region Implementation of ISomeInterface
public bool SomeBool { get; set; }
public string ValueIfSomeBool { get; set; }
#endregion
[NotNullValidator]
public string SomeOtherClassASpecificProp { get; set; }
}
And I have a Validation logic for the properties of this interface in a custom validator like so:
public class SomeInterfaceValidator : Validator<ISomeInterface>
{
public SomeInterfaceValidator (string tag)
: base(string.Empty, tag)
{
}
protected override string DefaultMessageTemplate
{
get { throw new NotImplementedException(); }
}
protected override void DoValidate(ISomeInterface objectToValidate, object currentTarget, string key, ValidationResults validationResults)
{
if (objectToValidate.SomeBool &&
string.IsNullOrEmpty(objectToValidate.ValIfSomeBool))
{
validationResults.AddResult(new ValidationResult("ValIfSomeBool cannot be null or empty when SomeBool is TRUE", currentTarget, key, string.Empty, null));
}
if (!objectToValidate.SomeBool &&
!string.IsNullOrEmpty(objectToValidate.ValIfSomeBool))
{
validationResults.AddResult(new ValidationResult("ValIfSomeBool must be null when SomeBool is FALSE", currentTarget, key, string.Empty, null));
}
}
}
And I have an attribute for applying this validator that I decorate ISomeInterface with.
[AttributeUsage(AttributeTargets.Interface)]
internal class SomeInterfaceValidatorAttribute : ValidatorAttribute
{
protected override Validator DoCreateValidator(Type targetType)
{
return new SomeInterfaceValidator(this.Tag);
}
}
When I call Validation.Validate it doesn't seem to be firing the validation in SomeInterfaceValidator. It does the validation specific to ClassA but not that of the interface ISomeInterface.
How do I get this to work?
EDIT:
I found one way to get this to work and that is to do SelfValidation, where I cast to ISomeInterface and validate like so. This will suffice, but still leaving the question open to see if there are any other ways to accomplish this.
[SelfValidation]
public void DoValidate(ValidationResults results)
{
results.AddAllResults(Validation.Validate((ISomeInterface)this));
}
This is a limitation of the Validation Application Block. Here is an article that describes how to add Validator inheritance for VAB.
One approach to validate interfaces is to use the ValidationFactory to create a Validator for the interface instead of using the Validator.Validate() static facade or CreateValidator() based on the concrete type. For example this should work given your approach:
var validator = ValidationFactory.CreateValidator<ISomeInterface>();
ValidationResults validationResults = validator.Validate(someInterfaceInstance);

Object created in inherited class used in base class

for object serialization I created a DataTransferObject-Pendant for each of my objects. Each original object gets a ToDTO()-method that returns the appropriate DTO-Object with the properties to be saved . Most of my original objects are inherited from an other, so I would want each inheritance-level to care for their own properties. A simple example:
class base
{
private string _name;
public DTO_base ToDTO()
{
DTO_base result = new DTO_base();
result.Name = _name;
return result;
}
}
The inherited class should override the ToDTO() method, calling the parents method and adding its own properties to be saved, like:
class inherited : base
{
private string _description;
public new DTO_inherited ToDTO()
{
DTO_inherited result = base.ToDTO();
result.Description = _description;
return result;
}
}
Obviously, this wont work, because base.ToDTO() returns a DTO_base object. Can anyone suggest, how this feature would be implemented best?
Thanks in advance,
Frank
If you really want to have the DTO creation logic in your business objects, I would go for a generic approach.
class Base<TDTO> where TDTO : BaseDTO, new()
{
private string _name;
public TDTO ToDTO()
{
TDTO dto = new TDTO();
SetupDTO(dto);
return dto;
}
protected virtual void SetupDTO(TDTO dto)
{
dto.Name = _name;
}
}
class Inherited : Base<InheritedDTO>
{
private string _description;
protected override void SetupDTO(TDTO dto)
{
base.SetupDTO(dto);
dto.Description = _description;
}
}
I would use a Template Method:
class baseCls
{
private string _name;
public DTO_base ToDTO()
{
DTO_base result = createDTO();
result.Name = _name;
setAdditionalData(result);
return result;
}
protected abstract DTO_base createDTO();
protected abstract void setAdditionalData(DTO_base dto);
}
class inherited : baseCls
{
private string _description;
protected override DTO_base createDTO() {
return new DTO_inerited();
}
protected override void setAdditionalData(DTO_base dto) {
inherited i = (DTO_inherited)dto;
i.Description = _Description;
}
}

Categories

Resources