MVC3 Validation with Lightspeed - validation

My ORM (LightSpeed) generates this for Animals table, with Name and Age. Using MVC3 and Razor
[Serializable]
[System.CodeDom.Compiler.GeneratedCode("LightSpeedModelGenerator", "1.0.0.0")]
[System.ComponentModel.DataObject]
[Table(IdColumnName="AnimalID", IdentityMethod=IdentityMethod.IdentityColumn)]
public partial class Animal : Entity<int>
{
[ValidatePresence]
[ValidateLength(0, 50)]
private string _name;
[ValidateComparison(ComparisonOperator.GreaterThan, 0)]
private int _age;
public const string NameField = "Name";
public const string AgeField = "Age";
[System.Diagnostics.DebuggerNonUserCode]
[Required] // ****I put this in manually to get Name required working
public string Name
{
get { return Get(ref _name, "Name"); }
set { Set(ref _name, value, "Name"); }
}
[System.Diagnostics.DebuggerNonUserCode]
public int Age
{
get { return Get(ref _age, "Age"); }
set { Set(ref _age, value, "Age"); }
}
With [Required] attribute added:
With no [Required] attribute added: (notice LightSpeed strange rendering of validation)
With name filled in:
In images above - the validation at the top is LightSpeed (put into ValidationSummary) and at the side is MVC3 (put into ValidationMessageFor)
Am only using Server Side validation currently.
Question: How do I get LightSpeed validation working well in MVC3?
I think it is something in this area http://www.mindscapehq.com/staff/jeremy/index.php/2009/03/aspnet-mvc-part4/
For the server side validation - you will want to use a custom model binder which emits the errors from LightSpeed validation more precisely rather than the leveraging the DefaultModelBinder behavior. Have a look at either directly using or adapting the EntityModelBinder from the community code library for Mvc
http://www.mindscapehq.com/forums/Thread.aspx?PostID=12051

See link http://www.mindscapehq.com/forums/Thread.aspx?ThreadID=4093
Jeremys answer (Mindscape have great support!)
public class EntityModelBinder2 : DefaultModelBinder
{
public static void Register(Assembly assembly)
{
ModelBinders.Binders.Add(typeof(Entity), new EntityModelBinder2());
foreach (Type type in assembly.GetTypes())
{
if (typeof(Entity).IsAssignableFrom(type))
{
ModelBinders.Binders.Add(type, new EntityModelBinder2());
}
}
}
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
object result = base.BindModel(controllerContext, bindingContext);
if (typeof(Entity).IsAssignableFrom(bindingContext.ModelType))
{
Entity entity = (Entity)result;
if (!entity.IsValid)
{
foreach (var state in bindingContext.ModelState.Where(s => s.Value.Errors.Count > 0))
{
state.Value.Errors.Clear();
}
foreach (var error in entity.Errors)
{
if (error.ErrorMessage.EndsWith("is invalid")) continue;
bindingContext.ModelState.AddModelError(error.PropertyName ?? "Custom", error.ErrorMessage);
}
}
}
return result;
}
}
and in Global.asax register using:
EntityModelBinder2.Register(typeof(MyEntity).Assembly);
The Register call sets up the model binder to be used for each entity type in your model assembly so modify as required.

You can get client side validation working with Lightspeed nightly builds from 04/04/2011 onwards.
Create a validator provider as follows:
public class LightspeedModelValidatorProvider : DataAnnotationsModelValidatorProvider
{
private string GetDisplayName(string name)
{
return name; // go whatever processing is required, eg decamelise, replace "_" with " " etc
}
protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
{
if(typeof(Entity).IsAssignableFrom(metadata.ContainerType))
{
List<Attribute> newAttributes = new List<Attribute>(attributes);
var attr = DataAnnotationBuilder.GetDataAnnotations(metadata.ContainerType, metadata.PropertyName, GetDisplayName(metadata.PropertyName));
newAttributes.AddRange(attr);
return base.GetValidators(metadata, context, newAttributes);
}
return base.GetValidators(metadata, context, attributes);
}
}
Then in Application_Start() add
ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new LightspeedModelValidatorProvider());

Related

Access custom attributes of .NET class inside custom json converter

In my project, I have written a custom json converter to trim the white-spaces present in the string property.
Here is an example of the typical class we will use,
public class Candidate
{
public string CandidateName { get; set; }
}
Here is my custom json converter
public class StringSanitizingConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue , JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.String)
if (reader.Value != null)
{
string sanitizedString = (reader.Value as string).Trim();
if (StringSanitizeOptions.HasFlag(StringSanitizeOptions.ToLowerCase))
sanitizedString = sanitizedString.ToLowerInvariant();
return sanitizedString;
}
return reader.Value;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var text = (string)value;
if (text == null)
writer.WriteNull();
else
writer.WriteValue(text.Trim());
}
}
With my custom converter I am now able to format the string by trimming any white-spaces present sent to the action methods using my 'Candidate' as one of its parameter.
public void Post(ComplexType complexTypeParameter){
}
Everything worked well so far. I later wanted to enhance this json converter to format the string properties based on the attributes set to the string property in the Candidate class. for example, assume I have written my candidate class like this,
public class Candidate
{
[StringSanitizingOptions(Option.ToLowerCase)]
public string CandidateName { get; set; }
}
And if I wanted to format the string properties of a class based on the custom attribute configuration inside the json converter , I am not able to access this custom attribute and its configuration inside the ReadJson method of the custom converter.
Here is what I have tried so far but with no luck,
Not present in the CustomAttributes property of the objectType
parameter sent to the ReadJson() method.
Was trying to see if I could extract the parent class of the property inside the ReadJson() method, so that I could apply reflection on the class to extract the custom attributes given to any of its property,but I could not extract that too.
The stack of containing object(s) is not made available to JsonConverter.ReadJson(), thus you cannot do what you want inside ReadJson().
Instead, what you can do is to create a custom contract resolver that applies an appropriately configured instance of StringSanitizingConverter based on the properties of the object for which a contract is being generated.
First, let's say your data model, attribute, and JsonConverter look like the following (where I had to modify a few things to make your code compile and include some additional test cases):
public class Candidate
{
[StringSanitizingOptions(Option.ToLowerCase)]
public string CandidateName { get; set; }
[StringSanitizingOptions(Option.DoNotTrim)]
public string StringLiteral { get; set; }
public string DefaultString { get; set; }
public List<string> DefaultStrings { get; set; }
}
[System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field | System.AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
public class StringSanitizingOptionsAttribute : System.Attribute
{
public Option StringSanitizeOptions { get; set; }
public StringSanitizingOptionsAttribute(Option stringSanitizeOptions)
{
this.StringSanitizeOptions = stringSanitizeOptions;
}
}
[Flags]
public enum Option
{
Default = 0,
ToLowerCase = (1<<0),
DoNotTrim = (1<<1),
}
public static class StringSanitizeOptionsExtensions
{
public static bool HasFlag(this Option options, Option flag)
{
return (options & flag) == flag;
}
}
public class StringSanitizingConverter : JsonConverter
{
readonly Option options;
public StringSanitizingConverter() : this(Option.Default) { }
public StringSanitizingConverter(Option options)
{
this.options = options;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.String)
if (reader.Value != null)
{
var sanitizedString = (reader.Value as string);
if (!options.HasFlag(Option.DoNotTrim))
sanitizedString = sanitizedString.Trim();
if (options.HasFlag(Option.ToLowerCase))
sanitizedString = sanitizedString.ToLowerInvariant();
return sanitizedString;
}
return reader.Value;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// WriteJson is never called with null
var text = (string)value;
if (!options.HasFlag(Option.DoNotTrim))
text = text.Trim();
writer.WriteValue(text);
}
}
Next, grab ConfigurableContractResolver from How to add metadata to describe which properties are dates in JSON.Net, and define the extension method JsonContractExtensions.AddStringConverters():
public static class JsonContractExtensions
{
public static JsonContract AddStringConverters(this JsonContract contract)
{
if (contract is JsonPrimitiveContract)
{
if (contract.UnderlyingType == typeof(string))
contract.Converter = new StringSanitizingConverter();
}
else if (contract is JsonObjectContract)
{
var objectContract = (JsonObjectContract)contract;
foreach (var property in objectContract.Properties)
{
if (property.PropertyType == typeof(string))
{
var attr = property.AttributeProvider.GetAttributes(typeof(StringSanitizingOptionsAttribute), true)
.Cast<StringSanitizingOptionsAttribute>()
.SingleOrDefault();
if (attr != null)
{
property.Converter = property.MemberConverter = new StringSanitizingConverter(attr.StringSanitizeOptions);
}
}
}
}
return contract;
}
}
public class ConfigurableContractResolver : DefaultContractResolver
{
// This contract resolver taken from the answer to
// https://stackoverflow.com/questions/46047308/how-to-add-metadata-to-describe-which-properties-are-dates-in-json-net
// https://stackoverflow.com/a/46083201/3744182
readonly object contractCreatedPadlock = new object();
event EventHandler<ContractCreatedEventArgs> contractCreated;
int contractCount = 0;
void OnContractCreated(JsonContract contract, Type objectType)
{
EventHandler<ContractCreatedEventArgs> created;
lock (contractCreatedPadlock)
{
contractCount++;
created = contractCreated;
}
if (created != null)
{
created(this, new ContractCreatedEventArgs(contract, objectType));
}
}
public event EventHandler<ContractCreatedEventArgs> ContractCreated
{
add
{
lock (contractCreatedPadlock)
{
if (contractCount > 0)
{
throw new InvalidOperationException("ContractCreated events cannot be added after the first contract is generated.");
}
contractCreated += value;
}
}
remove
{
lock (contractCreatedPadlock)
{
if (contractCount > 0)
{
throw new InvalidOperationException("ContractCreated events cannot be removed after the first contract is generated.");
}
contractCreated -= value;
}
}
}
protected override JsonContract CreateContract(Type objectType)
{
var contract = base.CreateContract(objectType);
OnContractCreated(contract, objectType);
return contract;
}
}
public class ContractCreatedEventArgs : EventArgs
{
public JsonContract Contract { get; private set; }
public Type ObjectType { get; private set; }
public ContractCreatedEventArgs(JsonContract contract, Type objectType)
{
this.Contract = contract;
this.ObjectType = objectType;
}
}
public static class ConfigurableContractResolverExtensions
{
public static ConfigurableContractResolver Configure(this ConfigurableContractResolver resolver, EventHandler<ContractCreatedEventArgs> handler)
{
if (resolver == null || handler == null)
throw new ArgumentNullException();
resolver.ContractCreated += handler;
return resolver;
}
}
Then, finally you can deserialize and serialize Candidate as follows:
var settings = new JsonSerializerSettings
{
ContractResolver = new ConfigurableContractResolver
{
}.Configure((s, e) => { e.Contract.AddStringConverters(); }),
};
var candidate = JsonConvert.DeserializeObject<Candidate>(json, settings);
var json2 = JsonConvert.SerializeObject(candidate, Formatting.Indented, settings);
Notes:
I don't know why the stack of containing object(s) is not available in ReadJson(). Possibilities include:
Simplicity.
A JSON object is "an unordered set of name/value pairs", so trying to access the containing .Net object while reading a property value isn't guaranteed to work, since the information required might not have been read in yet (and the parent might not even have been constructed).
Because a default instance of StringSanitizingConverter is applied to the contract generated for string itself, it is not necessary to add the converter to JsonSerializer.SettingsConverters. This in turn may lead to a small performance enhancement as CanConvert will no longer get called.
JsonProperty.MemberConverter was recently marked obsolete in Json.NET 11.0.1 but must be set to the same value as JsonProperty.Converter in previous versions of Json.NET. If you are using 11.0.1 or a more recent version you should be able to remove the setting.
You may want to cache the contract resolver for best performance.
To modify JsonSerializerSettings in asp.net-web-api, see JsonSerializerSettings and Asp.Net Core, Web API: Configure JSON serializer settings on action or controller level, How to set custom JsonSerializerSettings for Json.NET in MVC 4 Web API? or ASP.NET Core API JSON serializersettings per request, depending on your requirements and the version of the framework in use.
Sample working .Net fiddle here.

Default model example in Swashbuckle (Swagger)

I'm running ASP WebAPI 2 and successfully installed Swashbuckle. I am trying to figure out how one defines what the default schema values are?
For example, on the Swagger live demo site they changed the default value of pet to "doggie". They also defined the allowable values for status. (Live Demo)
I managed to get this working by following what's on this link:
https://github.com/domaindrivendev/Swashbuckle/issues/69#issuecomment-53953785
In short this is what needs to be done:
Create the classes SwaggerDefaultValue and AddDefaultValues as described in the link. Some changes that I did:
public class SwaggerDefaultValue : Attribute
{
public string Name { get; set; }
public string Value { get; set; }
public SwaggerDefaultValue(string value)
{
this.Value = value;
}
public SwaggerDefaultValue(string name, string value) : this(value)
{
this.Name = name;
}
}
public class AddDefaultValues : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
IDictionary<string, object> parameterValuePairs =
GetParameterValuePairs(apiDescription.ActionDescriptor);
foreach (var param in operation.parameters)
{
var parameterValuePair = parameterValuePairs.FirstOrDefault(p => p.Key.IndexOf(param.name, StringComparison.InvariantCultureIgnoreCase) >= 0);
param.#default = parameterValuePair.Value;
}
}
private IDictionary<string, object> GetParameterValuePairs(HttpActionDescriptor actionDescriptor)
{
IDictionary<string, object> parameterValuePairs = new Dictionary<string, object>();
foreach (SwaggerDefaultValue defaultValue in actionDescriptor.GetCustomAttributes<SwaggerDefaultValue>())
{
parameterValuePairs.Add(defaultValue.Name, defaultValue.Value);
}
foreach (var parameter in actionDescriptor.GetParameters())
{
if (!parameter.ParameterType.IsPrimitive)
{
foreach (PropertyInfo property in parameter.ParameterType.GetProperties())
{
var defaultValue = GetDefaultValue(property);
if (defaultValue != null)
{
parameterValuePairs.Add(property.Name, defaultValue);
}
}
}
}
return parameterValuePairs;
}
private static object GetDefaultValue(PropertyInfo property)
{
var customAttribute = property.GetCustomAttributes<SwaggerDefaultValue>().FirstOrDefault();
if (customAttribute != null)
{
return customAttribute.Value;
}
return null;
}
}
Edit your SwaggerConfig and add the AddDefaultValues class to the OperationFilters:
GlobalConfiguration.Configuration
.EnableSwagger(c => {
...
c.OperationFilter<AddDefaultValues>()
...
});
Now for the parameters I want default values I just add the following:
public IHttpActionResult Put([FromBody]Pet pet)
{
...
return Ok();
}
public class Pet {
[SwaggerDefaultValue("doggie")]
public string Name { get; set; }
[SwaggerDefaultValue("available")]
public string Status;
...
}
Well the code of vgaspar.trivix did not work completly for me, the default values did not get set for the schema. Also i got an NullPointerException. I managed to get it working as intended by editing the Apply method and manipulated the schemaRegistry like this:
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
if (operation.parameters == null)
return;
IDictionary<string, object> parameterValuePairs =
GetParameterValuePairs(apiDescription.ActionDescriptor);
foreach (var param in operation.parameters)
{
if (param.schema != null && param.schema.#ref != null)
{
string schemaName = param.schema.#ref.Split('/').LastOrDefault();
if (schemaRegistry.Definitions.ContainsKey(schemaName))
foreach (var props in schemaRegistry.Definitions[schemaName].properties)
{
if (parameterValuePairs.ContainsKey(props.Key))
props.Value.#default = parameterValuePairs[props.Key];
}
}
var parameterValuePair = parameterValuePairs.FirstOrDefault(p => p.Key.IndexOf(param.name, StringComparison.InvariantCultureIgnoreCase) >= 0);
param.#default = parameterValuePair.Value;
}
}
An example Model Schema can be defined by implementing ISchemaFilter and registering it using the following:
httpConfig
.EnableSwagger(c =>
{
c.SchemaFilter<AddSchemaExamples>()
});
An example implementation is provided here:
public class AddSchemaExamples : ISchemaFilter
{
public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type)
{
if (type == typeof(Product))
{
schema.example = new Product
{
Id = 123,
Type = ProductType.Book,
Description = "Treasure Island",
UnitPrice = 10.0M
};
}
}
}
Source: https://github.com/domaindrivendev/Swashbuckle/issues/162
I know this thread is quite old, but I wanted to share my solution which creates a custom constructor just for the Swagger example schema.
In my model:
/// <summary>
/// Supply a custom constructor for Swagger where you can apply defaults to control the example schema.
/// The constructor must have one parameter of type System.Reflection.ParameterInfo[].
/// Note: Setting a property to null will prevent it from showing in the Swagger example.
/// </summary>System.Reflection.ParameterInfo[].
/// </summary>
public class SwaggerConstructor : Attribute { }
In SwaggerConfig.cs:
c.SchemaFilter<ApplySchemaVendorExtensions>();
The schema extension:
public class ApplySchemaVendorExtensions : ISchemaFilter
{
public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type)
{
ConstructorInfo constructor = type.GetConstructors().FirstOrDefault(c => c.GetCustomAttribute<SwaggerConstructor>() != null);
if (constructor != null)
{
schema.example = constructor.Invoke(new object[] { constructor.GetParameters() });
}
}
}
Usage:
[SwaggerConstructor]
public MyClass(System.Reflection.ParameterInfo[] decoy) : base()
{
MyProperty = false;
}
Stumbled across this just now, you can also set the tag in the XML documentation, in one of my models, I have this defined
/// <summary>
/// Note content
/// </summary>
/// <example>Any text for a note.</example>
public string Note { get; set; }
which ends up looking like this in the swagger documentation when selecting "Try It Now"
Hope that helps someone!
Using .NET 5 with Swashbuckle.AspNetCore 5.6.3, the only way I could get this to work efficiently is this:
public class ExampleDocFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
string ToCamelCase(string name) => char.ToLowerInvariant(name[0]) + name.Substring(1);
if (schema.Properties == null) return;
var setProperties = context.Type.GetProperties().ToList().Where(f => f.GetCustomAttribute<DefaultValueAttribute>() != null).Where(f => schema.Properties.Any(n => n.Key.Equals(ToCamelCase(f.Name)))).ToDictionary(f => f, f => f.GetCustomAttribute<DefaultValueAttribute>());
foreach (var prop in setProperties) schema.Properties[ToCamelCase(prop.Key.Name)].Example = OpenApiAnyFactory.CreateFor(schema.Properties[ToCamelCase(prop.Key.Name)], prop.Value.Value);
}
}
To use this - in your startup.cs:
services.AddSwaggerGen(swagger => {
...
swagger.SchemaFilter<ExampleDocFilter>();
});

MediaTypeFormatter issue in Self-Hosted ASPNET WebAPI

I have implemented a custom MediaTypeFormatter in a SelfHosted AspNet WebAPI. I've used Unity.WebApi for dependency resolution. The controller classes only know about the interfaces implemented by the model classes, whereas, repositories give the concrete models as a result of actions.
The custom MediaTypeFormatter is inherited from BufferedMediaTypeFormatter as discussed here: http://www.asp.net/web-api/overview/formats-and-model-binding/media-formatters.
The problem is this media type formatter is not working. Even when I use to debug the code, the ReadFormStream method is never hit. Does someone know:
What could be the possible issue?
Do I need to tell the unity container for mapping of interfaces to model classes?
How would I get the reference to the dependency resolver inside the custom media type formatter?
Below is the code to add formatter:
var config = new SelfHostConfiguration("https://xxx.xxx.xxx.xxx:xxxx/");
config.Formatters.Add(new EntityTypeFormatter());
Below is the code for the EntityController:
public class EntityController : ApiController
{
private readonly IEntitiesRepository repository = null;
public EntityController(IEntitiesRepository repository)
{
if (repository == null)
{
throw new ArgumentNullException("repository");
}
this.repository = repository;
}
public IEnumerable<IEntity> Get()
{
return (IEnumerable<IEntity>)repository.Get();
}
}
Below is the code for the EntityRepository:
public class EntitiesRepository : IEntitiesRepository
{
public IEnumerable<IEntity> Get()
{
return new Entities[]
{
new Entity
{
Prop1 = "value for property 1",
Prop2 = "value for property 2",
Prop3 = "value for property 3"
},
new Entity
{
Prop1 = "value for property 1",
Prop2 = "value for property 2",
Prop3 = "value for property 3"
}
};
}
public IEntity Get(long id)
{
return new Entity
{
Prop1 = Convert.ToString(id),
Prop2 = "value for property 2",
Prop3 = "value for property 3"
};
}
}
Below is the implementation of the EntityMediaTypeFormatter class:
public class EntityMediaTypeFormatter : BufferedMediaTypeFormatter
{
public EntityMediaTypeFormatter()
: base()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/xml"));
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
}
public override bool CanReadType(Type type)
{
if (type == null)
{
throw new ArgumentNullException("type");
}
if (type is IEntity)
{
return true;
}
else if (type.IsGenericType)
{
return type.GenericTypeArguments.Single(a => a.GetType().Equals(typeof(IEntity))) != null;
}
return false;
}
public override bool CanWriteType(Type type)
{
if (type == null)
{
throw new ArgumentNullException("type");
}
if (type is IEntity)
{
return true;
}
return false;
}
public override object ReadFromStream(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
{
if (type.IsInterface)
{
type = GetConcreteType(type);
}
return base.ReadFromStream(type, readStream, content, formatterLogger);
}
public override void WriteToStream(Type type, object value, Stream writeStream, HttpContent content)
{
//nothing special for now...
base.WriteToStream(type, value, writeStream, content);
}
private Type GetConcreteType(Type type)
{
//TODO: Need to find a way to DependencyResolver to get the concrete type
return typeof(Entity);
}
}
Thanking in advance for any help.
Looks to me like one of the existing formatters is taking priority over yours. Either clear the existing formatter collection before adding yours, or insert yours at position 0.

Multiple attributes of same type fail when using Ninject's BindFilter<T>

As part of a identity-enabled authorization system, I'd like to use IAuhtorizationFilter and Attributes to restrict access to action methods in my controllers. I've got things working very well, partly due to help from the following resources:
Ninject Binding Attribute to Filter with Constructor Arguments
https://github.com/ninject/ninject.web.mvc/wiki/Filter-configurations
Custom Authorization MVC 3 and Ninject IoC
https://github.com/ninject/ninject.web.mvc/wiki/Dependency-injection-for-filters
However, when I try to decorate an action method with more than one of my attributes, I get an exception as follows (sorry for the formatting):
[InvalidOperationException: Sequence contains more than one element]
System.Linq.Enumerable.Single(IEnumerable`1 source) +2691369
Ninject.Web.Mvc.FilterBindingSyntax.c__DisplayClass15`1.b__14(IContext ctx, ControllerContext controllerContext, ActionDescriptor actionDescriptor) in c:\Projects\Ninject\Maintenance2.2\ninject.web.mvc\mvc3\src\Ninject.Web.Mvc\FilterBindingSyntax\FilterFilterBindingBuilder.cs:379
Ninject.Web.Mvc.FilterBindingSyntax.c__DisplayClass12.b__11(IContext ctx) in c:\Projects\Ninject\Maintenance2.2\ninject.web.mvc\mvc3\src\Ninject.Web.Mvc\FilterBindingSyntax\FilterFilterBindingBuilder.cs:358
Ninject.Parameters.c__DisplayClass6.b__4(IContext ctx, ITarget target) in c:\Projects\Ninject\Maintenance2.2\ninject\src\Ninject\Parameters\Parameter.cs:60
Ninject.Parameters.Parameter.GetValue(IContext context, ITarget target) in c:\Projects\Ninject\Maintenance2.2\ninject\src\Ninject\Parameters\Parameter.cs:88
Ninject.Activation.Providers.StandardProvider.GetValue(IContext context, ITarget target) in c:\Projects\Ninject\Maintenance2.2\ninject\src\Ninject\Activation\Providers\StandardProvider.cs:97
Ninject.Activation.Providers.c__DisplayClass2.b__1(ITarget target) in c:\Projects\Ninject\Maintenance2.2\ninject\src\Ninject\Activation\Providers\StandardProvider.cs:81
...
Here is a very simplified version of my code that demonstrates the problem in an MVC3 app:
Attribute:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class SampleAttribute : Attribute
{
private Guid typeId;
public bool IsAllowed { get; set; }
public SampleAttribute(bool IsAllowed)
{
this.IsAllowed = IsAllowed;
this.typeId = new Guid();
}
public override object TypeId
{
get
{
return (object)typeId;
}
}
}
Filter:
public class SampleFilter : IAuthorizationFilter, IMvcFilter
{
private bool isAllowed;
public bool AllowMultiple
{
get { return true; }
}
public int Order
{
get { return 0; }
}
public SampleFilter(bool isAllowed)
{
this.isAllowed = isAllowed;
}
public void OnAuthorization(AuthorizationContext filterContext)
{
if (!isAllowed)
throw new Exception("unauthorized");
}
}
Controller:
public class HomeController : Controller
{
[Sample(true)]
[Sample(false)]
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}
}
The controller method above works as expected if one or the other of the Sample attributes on the Index() method is removed. Having both in place, generates the exception. I realize that in this simplified example, there isn't a situation that would call for both attributes, but it's simply for illustration.
What am I missing?
This is a known issue of Ninject 2.2. Please use 3.0 instead.
https://github.com/ninject/ninject.web.mvc/blob/master/mvc3/ReleaseNotes.txt

MVC3 custom unobtrusive validation - force validation from checkbox

I have the following class used for custom validation:
[AttributeUsage(AttributeTargets.Property, AllowMultiple=false, Inherited=true)]
public sealed class RequiredIfAnyTrueAttribute : ValidationAttribute, IClientValidatable
{
private const string DefaultErrorMessage = "{0} is required";
public List<string> OtherProperties { get; private set; }
public RequiredIfAnyTrueAttribute(string otherProperties)
: base(DefaultErrorMessage)
{
if (string.IsNullOrEmpty(otherProperties))
throw new ArgumentNullException("otherProperty");
OtherProperties = new List<string>(otherProperties.Split(new char[] { '|', ',' }));
}
public override string FormatErrorMessage(string name)
{
return string.Format(ErrorMessageString, name);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null)
{
foreach (string s in OtherProperties)
{
var otherProperty = validationContext.ObjectType.GetProperty(s);
var otherPropertyValue = otherProperty.GetValue(validationContext.ObjectInstance, null);
if (otherPropertyValue.Equals(true))
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var clientValidationRule = new ModelClientValidationRule()
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "requiredifanytrue"
};
clientValidationRule.ValidationParameters.Add("otherproperties", string.Join("|",OtherProperties));
return new[] { clientValidationRule };
}
}
My ViewModel is:
public class SampleViewModel
{
public bool PropABC { get; set; }
public bool PropXYZ { get; set; }
[RequiredIfAnyTrue("PropABC|PropXYZ")]
public int? TestField { get; set; }
}
When my strongly typed view renders, everything sees to work fine. If PropABC or PropXYZ is selected then I am required to enter a value for TestField. Both client and server-side validation is functional.
However, given the following sequence of events:
check PropABC
submit form
client-side validation fires for TestField required
uncheck PropABC
client validation does not re-fire and validation message
remains until form submit
In order to resolve #5 I would typically attach click events to the checkboxes via jquery onready to refire the validation.
Is there a preferred/recommended way to manually force client-side validation given MVC3 + unobstrusive + jquery?
Shawn, attaching to events is the best approach to get validation to refire.
I would suggest creating a class called "validate" (or something along those lines), adding it to each element to be validated, and then use jQuery to attach to the click and blur events (and possibly the change event) of each element with that class, and validate the element like so:
$("form").validate().element(this);
Do you need to write your own attributes? If not I think you may be able to avoid "reinventing the wheel"
FoolProof works great. you get get it as a NuGet package.
NuGet: install-package foolproof
It includes a lot of greate attributes for various combinations of on-the-fly required fields and such.
FoolProof is still in beta and does not work with nested viewmodel, also with arrays

Resources