I've searched high an low of SO to find a solution for my problem.
I've found several answers for when it comes to simple expressions like
var exp1 Expression<Func<T, bool>> x => x.Name == "MyName"
But I'm having trouble when the expressions are like:
var exp1 Expression<Func<T, bool>> x => x.Category.Name == "Coupe"
For the simple ones, I am able to convert any expression from one type (T) to another (TT), I need to do it also in the other cases, more complex...
Anyone who can help with some pointers? Here is what I've got so far:
private class CustomVisitor<T> : ExpressionVisitor
{
private readonly ParameterExpression mParameter;
public CustomVisitor(ParameterExpression parameter)
{
mParameter = parameter;
}
//this method replaces original parameter with given in constructor
protected override Expression VisitParameter(ParameterExpression node)
{
return mParameter;
}
private int counter = 0;
/// <summary>
/// Visits the children of the <see cref="T:System.Linq.Expressions.MemberExpression" />.
/// </summary>
/// <param name="node">The expression to visit.</param>
/// <returns>
/// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
/// </returns>
/// <exception cref="System.NotImplementedException"></exception>
protected override Expression VisitMember(MemberExpression node)
{
counter++;
System.Diagnostics.Debug.WriteLine("{0} - {1}", node.ToString(), counter);
try
{
//only properties are allowed if you use fields then you need to extend
// this method to handle them
if (node.Member.MemberType != System.Reflection.MemberTypes.Property)
throw new NotImplementedException();
//name of a member referenced in original expression in your
//sample Id in mine Prop
var memberName = node.Member.Name;
//find property on type T (=PersonData) by name
var otherMember = typeof(T).GetProperty(memberName);
//visit left side of this expression p.Id this would be p
var inner = Visit(node.Expression);
return Expression.Property(inner, otherMember);
}
catch (Exception ex)
{
return null;
}
}
}
Utility method:
public static Expression<Func<TDestin, T>> ConvertTypesInExpression<TSource, TDestin, T>(Expression<Func<TSource, T>> source)
{
var param = Expression.Parameter(typeof(TDestin));
var body = new CustomVisitor<TDestin>(param).Visit(source.Body);
Expression<Func<TDestin, T>> lambda = Expression.Lambda<Func<TDestin, T>>(body, param);
return lambda;
}
And it's being used like this:
var changedFilter = ConvertTypesInExpression<ClientNotificationRuleDto, ClientNotificationRule, bool>(filterExpression);
So if anyone can help with some ideias or pointers, that would be great!
Analyze this test:
class Replaced
{
public Inner Inner { get; set; }
}
class Inner
{
public string Name { get; set; }
}
class Replacing
{
public Inner Inner { get; set; }
}
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
var parameter = Expression.Parameter(typeof(Replacing));
var visitor = new CustomVisitor(parameter);
Expression<Func<Replaced, bool>> expression = x => x.Inner.Name == "ss";
var resultExpression = (Expression<Func<Replacing, bool>>)visitor.Visit(expression);
var function = resultExpression.Compile();
var result = function(new Replacing
{
Inner = new Inner
{
Name = "ss"
}
});
Assert.IsTrue(result);
}
}
internal class CustomVisitor : ExpressionVisitor
{
private readonly ParameterExpression mParameter;
private int counter = 0;
public CustomVisitor(ParameterExpression parameter)
{
mParameter = parameter;
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
return Expression.Lambda(
Visit(node.Body),
node.Parameters.Select(x => (ParameterExpression)Visit(x)).ToArray());
//or simpler but less generic
//return Expression.Lambda(Visit(node.Body), mParameter);
}
//this method will be called twice first for Name and then for Inner
protected override Expression VisitMember(MemberExpression node)
{
counter++;
System.Diagnostics.Debug.WriteLine("{0} - {1}", node.ToString(), counter);
if (node.Member.MemberType != System.Reflection.MemberTypes.Property)
throw new NotImplementedException();
var memberName = node.Member.Name;
var inner = Visit(node.Expression);
var otherMember = inner.Type.GetProperty(memberName);
return Expression.Property(inner, otherMember);
}
protected override Expression VisitParameter(ParameterExpression node)
{
return mParameter;
}
}
Note that visit member is called twice and must react accordingly for both calls. Also you need to override the lambda creation as it would fail in parameter replacement.
PS: Never catch base class Exception its just bad practice and the panic return null on exception is just wrong.
With the precious help from #Rafal and insights from this, I managed to achieve a solution for my needs
public static class EXpressionTreeTools
{
#region ConvertTypesInExpression
/// <summary>
/// Converts the types in the expression.
/// </summary>
/// <typeparam name="TSource">The source type (the "replacee").</typeparam>
/// <typeparam name="TDestin">The destiny type (the replacer).</typeparam>
/// <typeparam name="T">The type of the result fo the expression evaluation</typeparam>
/// <param name="source">The source expression.</param>
/// <returns></returns>
public static Expression<Func<TDestin, T>> ConvertTypesInExpression<TSource, TDestin, T>(Expression<Func<TSource, T>> source)
{
var parameter = Expression.Parameter(typeof(TDestin));
var visitor = new CustomVisitor(parameter);
//Expression<Func<TSource, bool>> expression = x => x.Inner.Name == "ss";
Expression<Func<TDestin, T>> resultExpression = (Expression<Func<TDestin, T>>)visitor.Visit(source);
return resultExpression;
}
#endregion
#region CustomVisitor
/// <summary>
/// A custom "visitor" class to traverse expression trees
/// </summary>
/// <typeparam name="T"></typeparam>
internal class CustomVisitor : ExpressionVisitor
{
private readonly ParameterExpression mParameter;
public CustomVisitor(ParameterExpression parameter)
{
mParameter = parameter;
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
return Expression.Lambda(
Visit(node.Body),
node.Parameters.Select(x => (ParameterExpression)Visit(x)).ToArray());
//or simpler but less generic
//return Expression.Lambda(Visit(node.Body), mParameter);
}
//this method will be called twice first for Name and then for Inner
protected override Expression VisitMember(MemberExpression node)
{
if (node.Member.MemberType != System.Reflection.MemberTypes.Property)
//throw new NotImplementedException();
{
Expression exp = this.Visit(node.Expression);
if (exp == null || exp is ConstantExpression) // null=static member
{
object #object = exp == null ? null : ((ConstantExpression)exp).Value;
object value = null; Type type = null;
if (node.Member is FieldInfo)
{
FieldInfo fi = (FieldInfo)node.Member;
value = fi.GetValue(#object);
type = fi.FieldType;
}
else if (node.Member is PropertyInfo)
{
PropertyInfo pi = (PropertyInfo)node.Member;
if (pi.GetIndexParameters().Length != 0)
throw new ArgumentException("cannot eliminate closure references to indexed properties");
value = pi.GetValue(#object, null);
type = pi.PropertyType;
}
return Expression.Constant(value, type);
}
else // otherwise just pass it through
{
return Expression.MakeMemberAccess(exp, node.Member);
}
}
var memberName = node.Member.Name;
var inner = Visit(node.Expression);
var otherMember = inner.Type.GetProperty(memberName);
return Expression.Property(inner, otherMember);
}
protected override Expression VisitParameter(ParameterExpression node)
{
return mParameter;
}
}
#endregion
}
Related
I have a linq query with multiple conditional where clauses. The query returns all data in the table when there is no filter in the where clause. How to make the linq query returns 0 record in the first time when there is no filter in the where clause? See my code below:
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Web.Http;
using API.Models;
using System.Linq.Expressions;
namespace API.Controllers
{
//api code starts here
public class MYTABLEController : ApiController
{
private DataModel db = new DataModel();
//GET /MYTABLE
public List<MYTABLE> Get(string filter1 = null,string filter2=null)
{
IQueryable<MYTABLE> qry = db.MYTABLE.AsQueryable();
var searchPredicate = PredicateBuilder.False<MYTABLE>();
if (!string.IsNullOrWhiteSpace(filter1))
{
searchPredicate = searchPredicate.And(a => a.COLUMN1==(filter1);
}
if (!string.IsNullOrWhiteSpace(filter2))
{
searchPredicate = searchPredicate.And(a => a.COLUMN2==(filter2);
}
return qry.Where(searchPredicate).ToList();
}
//PredicateBuilder https://petemontgomery.wordpress.com/2011/02/10/a-universal-predicatebuilder/
public static class PredicateBuilder
{
/// <summary>
/// Creates a predicate that evaluates to true.
/// </summary>
public static Expression<Func<T, bool>> True<T>() { return param => true; }
/// <summary>
/// Creates a predicate that evaluates to false.
/// </summary>
public static Expression<Func<T, bool>> False<T>() { return param => false; }
/// <summary>
/// Creates a predicate expression from the specified lambda expression.
/// </summary>
public static Expression<Func<T, bool>> Create<T>(Expression<Func<T, bool>> predicate) { return predicate; }
/// <summary>
/// Combines the first predicate with the second using the logical "and".
/// </summary>
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
{
return first.Compose(second, Expression.AndAlso);
}
/// <summary>
/// Combines the first predicate with the second using the logical "or".
/// </summary>
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
{
return first.Compose(second, Expression.OrElse);
}
/// <summary>
/// Negates the predicate.
/// </summary>
public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expression)
{
var negated = Expression.Not(expression.Body);
return Expression.Lambda<Func<T, bool>>(negated, expression.Parameters);
}
/// <summary>
/// Combines the first expression with the second using the specified merge function.
/// </summary>
static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
{
// zip parameters (map from parameters of second to parameters of first)
var map = first.Parameters
.Select((f, i) => new { f, s = second.Parameters[i] })
.ToDictionary(p => p.s, p => p.f);
// replace parameters in the second lambda expression with the parameters in the first
var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);
// create a merged lambda expression with parameters from the first expression
return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
}
class ParameterRebinder : ExpressionVisitor
{
readonly Dictionary<ParameterExpression, ParameterExpression> map;
ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
{
this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
}
public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
{
return new ParameterRebinder(map).Visit(exp);
}
protected override Expression VisitParameter(ParameterExpression p)
{
ParameterExpression replacement;
if (map.TryGetValue(p, out replacement))
{
p = replacement;
}
return base.VisitParameter(p);
}
}
}
Update this method like this:
public List<MYTABLE> Get(string filter1 = null,string filter2=null)
{
if(string.IsNullOrWhiteSpace(filter1) && string.IsNullOrWhiteSpace(filter2))
return new List<MYTABLE>();
IQueryable<MYTABLE> qry = db.MYTABLE.AsQueryable();
var searchPredicate = PredicateBuilder.False<MYTABLE>();
if (!string.IsNullOrWhiteSpace(filter1))
{
searchPredicate = searchPredicate.And(a => a.COLUMN1==(filter1);
}
if (!string.IsNullOrWhiteSpace(filter2))
{
searchPredicate = searchPredicate.And(a => a.COLUMN2==(filter2);
}
return qry.Where(searchPredicate).ToList();
}
First, I'd use a predicate builder for this. For example this one.
That would turn your code into:
IQyeryable<MyTable> qry = db.myTable;
var predicate = PredicateBuilder.True<MyTable>();
if (!string.IsNullOrWhiteSpace(filter1))
{
predicate = predicate.And(a => a.column1.Contains(filter1));
}
if (!string.IsNullOrWhiteSpace(filter2))
{
predicate = predicate.And(a => a.column2.Contains(filter2));
}
...
qry = qry.Where(predicate);
Now it's easy to check if any predicates were added. If not, predicate is nothing but a => true, which means that its body is just a ConstantExpression (value: true). You can add this check just above the last line:
if (predicate.Body is ConstantExpression)
{
predicate = PredicateBuilder.False<MyTable>();
}
(Of course provided that you don't replace predicate by another predicate, another constant expression, but I guess that wouldn't make much sense).
You can create an extension method:
public static IQueryable<T> WhereIf<T>(
this IQueryable<T> source, bool condition,
Expression<Func<T, bool>> predicate)
{
return condition ? source.Where(predicate) : source;
}
And use it in a following fashion:
using static System.String;
...
public List<MYTABLE> Get(string filter1 = null,string filter2=null)
{
return db.MYTABLE
.WhereIf(!IsNullOrEmpty(filter1), y => y.COLUMN1 == filter1)
.WhereIf(!IsNullOrEmpty(filter2), y => y.COLUMN2 == filter2)
.ToList();
}
Below is the code for a simple JsonModelBinder I created for an ASP.NET Core Mvc app. Is there a simple way to recursively validate the model, its properties and the properties of its properties and so on?
JsonModelBinder
public class JsonModelBinder : IModelBinder
{
static readonly JsonSerializerSettings settings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
};
public Task BindModelAsync(ModelBindingContext bindingContext)
{
try
{
var json = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).Values;
if (json.Count > 0)
{
var model = JsonConvert.DeserializeObject(json, bindingContext.ModelType, settings);
// TODO: Validate complex model
bindingContext.Result = ModelBindingResult.Success(model);
}
else
{
bindingContext.Result = ModelBindingResult.Success(null);
}
}
catch (JsonException ex)
{
bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex.Message);
bindingContext.Result = ModelBindingResult.Failed();
}
return Task.CompletedTask;
}
}
Example model
public class Foo {
[Required]
public string Name { get; set; }
public List<Bar> Bars { get; set; }
public Baz Baz { get; set; }
}
public class Bar {
[Required]
public string Name { get; set; }
}
public class Baz {
[Required]
public string Name { get; set; }
}
Controller action
public async Task<IActionResult> Edit(Guid id, [Required, ModelBinder(typeof(JsonModelBinder))] Foo foo) {
if (ModelState.IsValid) {
// Do stuff
}
else {
return View(foo);
}
}
I created the following recursive data annotations validator, and used it like below. Based on some feedback I received, it appears that there is nothing like this built into .NET Core at this time.
Recursive data annotations validator:
public static class RecursiveValidator
{
/// <summary>
/// Recursively validates <paramref name="instance"/>.
/// </summary>
/// <param name="instance"></param>
/// <param name="prefix"></param>
/// <param name="validationContext"></param>
/// <param name="results"></param>
/// <param name="validateAllProperties"></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"><paramref name="instance"/> is null</exception>
public static bool TryValidateObject(object instance, IServiceProvider serviceProvider, IDictionary<object, object> items, ICollection<ValidationResult> results, bool validateAllProperties, string prefix)
{
if (instance is null)
{
throw new ArgumentNullException(nameof(instance));
}
var tempResults = new List<ValidationResult>();
ValidationContext validationContext = new ValidationContext(instance, serviceProvider, items);
var isValid = Validator.TryValidateObject(instance, validationContext, tempResults, validateAllProperties: validateAllProperties);
foreach (var item in tempResults)
{
IEnumerable<string> memberNames = item.MemberNames.Select(name => (!string.IsNullOrEmpty(prefix) ? prefix + "." : "") + name);
results.Add(new ValidationResult(item.ErrorMessage, memberNames));
}
foreach (var prop in instance.GetType().GetProperties())
{
if (prop.GetSetMethod() == null)
{
continue;
}
else if (prop.PropertyType.IsClass && prop.PropertyType != typeof(string))
{
var value = prop.GetValue(instance);
if (value == null)
{
continue;
}
else if (value is IEnumerable<object> list)
{
var memberPrefix = (!string.IsNullOrEmpty(prefix) ? prefix + "." : "") + prop.Name;
int i = 0;
foreach (var item in list)
{
if (!TryValidateObject(item, serviceProvider, items, results, validateAllProperties: validateAllProperties, prefix: $"{memberPrefix}[{i}]"))
{
isValid = false;
}
i++;
}
}
else
{
var memberPrefix = (!string.IsNullOrEmpty(prefix) ? prefix + "." : "") + prop.Name;
if (!TryValidateObject(value, serviceProvider, items, results, validateAllProperties: validateAllProperties, prefix: memberPrefix))
{
isValid = false;
}
}
}
}
return isValid;
}
}
Example of using it to add model state errors to the bindingContext:
var validationResults = new List<ValidationResult>();
if (!RecursiveValidator.TryValidateObject(model, bindingContext.HttpContext.RequestServices, null, validationResults, validateAllProperties: true, prefix: bindingContext.ModelName))
{
foreach (var result in validationResults)
{
foreach (var member in result.MemberNames)
{
bindingContext.ModelState.AddModelError(member, result.ErrorMessage);
}
}
}
I'm trying to add "OPTION(RECOMPILE)" to the end of some of my NHibernate queries. I found the following post:
http://www.codewrecks.com/blog/index.php/2011/07/23/use-sql-server-query-hints-with-nhibernate-hql-and-icriteria/
This describes how I can add an interceptor to append the SQL. However they are using ICriteria whereas I use LINQ to query my data. Ideally I'd like to be able to say something like:
var query = session.Query<Foo>().OptionRecompile().ToList();
I was wondering if it's possible to add an extension method to IQueryable which will inject some string into the query which I can then detect for in my interceptor. This is similar to the approach used in the article above where they add a comment and detect for that.
For further information. I've dealt with LINQ extensions before and I know you can add extension properties/methods using a HQL generator. But from my understanding this will only allow me to say:
var query = session.Query<Foo>().Where(f => f.Bar.OptionRecompile()).ToList();
This isn't ideal and seems more of a hack. I'd appreciate it if someone can help. Thanks
I ran into this issue recently, too. We came up with a fairly decent/robust solution. The important thing is that it utilizes Rhino.Commons.LocalData to provide an execution scope.
// First part
using System;
using System.Collections;
using System.Web;
using Rhino.Commons.LocalDataImpl;
namespace Rhino.Commons
{
/// <summary>
/// This class is key for handling local data, data that is private
/// to the current context, be it the current thread, the current web
/// request, etc.
/// </summary>
public static class Local
{
static readonly ILocalData current = new LocalData();
static readonly object LocalDataHashtableKey = new object();
private class LocalData : ILocalData
{
[ThreadStatic]
static Hashtable thread_hashtable;
private static Hashtable Local_Hashtable
{
get
{
if (!RunningInWeb)
{
return thread_hashtable ??
(
thread_hashtable = new Hashtable()
);
}
Hashtable web_hashtable = HttpContext.Current.Items[LocalDataHashtableKey] as Hashtable;
if(web_hashtable==null)
{
HttpContext.Current.Items[LocalDataHashtableKey] = web_hashtable = new Hashtable();
}
return web_hashtable;
}
}
public object this[object key]
{
get { return Local_Hashtable[key]; }
set { Local_Hashtable[key] = value; }
}
public void Clear()
{
Local_Hashtable.Clear();
}
}
/// <summary>
/// Gets the current data
/// </summary>
/// <value>The data.</value>
public static ILocalData Data
{
get { return current; }
}
/// <summary>
/// Gets a value indicating whether running in the web context
/// </summary>
/// <value><c>true</c> if [running in web]; otherwise, <c>false</c>.</value>
public static bool RunningInWeb
{
get { return HttpContext.Current != null; }
}
}
}
// Second part
using System;
using Rhino.Commons;
namespace IDL.Core.Util.NHibernate
{
public class NhSqlAppender : IDisposable
{
private static string sql;
private int usages = 1;
public NhSqlAppender()
{
}
public NhSqlAppender(string sqlToAppend)
{
sql = sqlToAppend;
}
public static NhSqlAppender Append(string sqlToAppend)
{
var currentAppender = Current;
if (currentAppender == null)
{
Current = new NhSqlAppender(sqlToAppend);
currentAppender = Current;
}
else
currentAppender.IncrementUsages();
return currentAppender;
}
public static NhSqlAppender Current
{
get { return Local.Data["NhSqlAppender"] as NhSqlAppender; }
protected set { Local.Data["NhSqlAppender"] = value; }
}
public static string Sql
{
get { return (IsValid) ? sql : string.Empty; }
}
public static bool AppendSql
{
get { return IsValid; }
}
public void IncrementUsages()
{
++usages;
}
public void DecrementUsages()
{
--usages;
}
private static bool IsValid
{
get { return (Current != null && !string.IsNullOrWhiteSpace(sql)); }
}
public void Dispose()
{
if (usages <= 1)
Current = null;
else
DecrementUsages();
}
}
}
// Third part
namespace IDL.Core.Util.NHibernate
{
public class NhQueryHint : NhSqlAppender
{
public static NhSqlAppender Recompile()
{
return Append("OPTION(RECOMPILE)");
}
}
}
// Fourth part
using System;
using IDL.Core.Util.NHibernate;
using NHibernate;
namespace IDL.Core.Configuration
{
[Serializable]
public class NhSqlAppenderInterceptor : EmptyInterceptor
{
public override NHibernate.SqlCommand.SqlString OnPrepareStatement(NHibernate.SqlCommand.SqlString sql)
{
if (NhSqlAppender.AppendSql)
return sql.Insert(sql.Length, (" " + NhSqlAppender.Sql));
return base.OnPrepareStatement(sql);
}
}
}
// Fifth part
// You need to register the interceptor with NHibernate
// cfg = NHibernate.Cfg.Configuration
cfg.SetInterceptor(new NhSqlAppenderInterceptor());
// Finally, usage
using (NhQueryHint.Recompile())
var results = IQueryable<T>.ToList();
You'll have to modify to fit your environment. Hope it helps!
The accepted answer above by #jvukovich helped my out a lot. I built upon his answer by encapsulating the usage of the Recompile hint in an extension method as shown below.
// Extension Method
public static class NhQueryHintExtensions
{
public static TReturn Recompile<T, TReturn>(this IQueryable<T> queryable, Func<IQueryable<T>, TReturn> toFunction)
{
using (NhQueryHint.Recompile())
{
return toFunction(queryable);
}
}
}
// Usage
var results = IQueryable<T>.Recompile(x => x.ToList());
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>();
});
I need to implement a RequiredIF validator which depends on two values, a checkbox and a value selected from a dropdown.
I need if possible something like
[RequiredIf("Property1", true,"Property2,"value", ErrorMessageResourceName = "ReqField", ErrorMessageResourceType = typeof(RegisterUser))]
Here is code to create your own RequiredIf and RequiredIfNot Custom Validator.
Simply add additional code if you want to check for 2 values.
RequiredIf
public class RequiredIfAttribute : ValidationAttribute, IClientValidatable
{
private readonly RequiredAttribute _innerAttribute = new RequiredAttribute();
internal string _dependentProperty;
internal object _targetValue;
public RequiredIfAttribute(string dependentProperty, object targetValue)
{
_dependentProperty = dependentProperty;
_targetValue = targetValue;
}
/// <summary>
/// Returns if the given validation result is valid. It checks if the RequiredIfAttribute needs to be validated
/// </summary>
/// <param name="value">Value of the control</param>
/// <param name="validationContext">Validation context</param>
/// <returns></returns>
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var field = validationContext.ObjectType.GetProperty(_dependentProperty);
if (field != null)
{
var dependentValue = field.GetValue(validationContext.ObjectInstance, null);
if ((dependentValue == null && _targetValue == null) || (dependentValue.ToString() == _targetValue.ToString()))
{
if (!_innerAttribute.IsValid(value))
{
return new ValidationResult(ErrorMessage);
}
}
return ValidationResult.Success;
}
else
{
throw new ValidationException("RequiredIf Dependant Property " + _dependentProperty + " does not exist");
}
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = ErrorMessageString,
ValidationType = "requiredif",
};
rule.ValidationParameters["dependentproperty"] = (context as ViewContext).ViewData.TemplateInfo.GetFullHtmlFieldId(_dependentProperty);
rule.ValidationParameters["desiredvalue"] = _targetValue is bool ? _targetValue.ToString().ToLower() : _targetValue;
yield return rule;
}
}
RequireIfNot:
public class RequiredIfNotAttribute : ValidationAttribute, IClientValidatable
{
private readonly RequiredAttribute _innerAttribute = new RequiredAttribute();
internal string _dependentProperty;
internal object _targetValue;
public RequiredIfNotAttribute(string dependentProperty, object targetValue)
{
_dependentProperty = dependentProperty;
_targetValue = targetValue;
}
/// <summary>
/// Returns if the given validation result is valid. It checks if the RequiredIfAttribute needs to be validated
/// </summary>
/// <param name="value">Value of the control</param>
/// <param name="validationContext">Validation context</param>
/// <returns></returns>
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var field = validationContext.ObjectType.GetProperty(_dependentProperty);
if (field != null)
{
var dependentValue = field.GetValue(validationContext.ObjectInstance, null);
if ((dependentValue == null && _targetValue == null) || (dependentValue.ToString() != _targetValue.ToString()))
{
if (!_innerAttribute.IsValid(value))
{
return new ValidationResult(ErrorMessage);
}
}
return ValidationResult.Success;
}
else
{
throw new ValidationException("RequiredIfNot Dependant Property " + _dependentProperty + " does not exist");
}
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = ErrorMessageString,
ValidationType = "requiredifnot",
};
rule.ValidationParameters["dependentproperty"] = (context as ViewContext).ViewData.TemplateInfo.GetFullHtmlFieldId(_dependentProperty);
rule.ValidationParameters["desiredvalue"] = _targetValue is bool ? _targetValue.ToString().ToLower() : _targetValue;
yield return rule;
}
}
Usage:
In your model use the following DataAnnotation.
[RequiredIf("IsRequired", true, ErrorMessage = "First Name is required.")]
You have to make custom validator. it is the bets way to achieve that. I hope this helps custom validator