c# validation automation in data anontations - c#

Summary: Im working with C# 4.5 version and more specifically in Web API.
Im trying to build an object and wrap it with attributes so when I receive a HTTP POST request, validation will be made in modelState.
a little example before code:
Lets say I have this following request object
public class PlayerRequest
{
[TeamId]
public string TeamId {set;get;}
[UserId]
public string UserId {set;get;}
}
now, I want to be able to just add an attribute to the class and it will check if class contains TeamId and UserId and if so, validate in db that in fact user has access to team.
so lets say, the declaration will be something like:
[PairsValidate]
public class TeamRequest
{
//...
}
What I aim to create is not a specific validation for TeamId and UserId but to create some sort of a pool of attribute pairs and run a simple loop to detect them and validate.
code so far:
[AttributeUsage(AttributeTargets.Class)]
public sealed class AccessValidator : ValidationAttribute
{
private readonly AttributePairValidator[] _validators =
{
UserIdTeamIdValidator.GetInstance(AccessManager.UserAccessToTeam)
};
public override bool IsValid(object value)
{
PropertyInfo[] properties = value.GetType().GetProperties();
foreach (PropertyInfo p in properties)
{
foreach (AttributePairValidator valPair in _validators)
{
valPair.Accept(/* here is the problem */ , p.GetValue as string);
}
}
}
}
public class AttributePairValidator
{
protected string fieldA;
protected string fieldB;
protected Func<string, string, Task<bool>> _validationMethod;
protected static object _lockObj = new object();
protected AttributePairValidator(Func<string, string, Task<bool>> validationMethod)
{
_validationMethod = validationMethod;
}
public bool Accept (ValidationAttribute attr, string val)
{
return true;
}
protected async Task<bool> Check()
{
if (!String.IsNullOrEmpty(fieldA) && !String.IsNullOrEmpty(fieldB))
return await _validationMethod(fieldA, fieldB);
return true;
}
}
public sealed class UserIdTeamIdValidator : AttributePairValidator
{
private static UserIdTeamIdValidator _instance = null;
private UserIdTeamIdValidator(Func<string, string, Task<bool>> validationMethod) : base (validationMethod)
{
}
public static UserIdTeamIdValidator GetInstance(Func<string, string, Task<bool>> validationMethod)
{
lock (_lockObj)
{
if (_instance == null)
_instance = new UserIdTeamIdValidator(validationMethod);
}
return _instance;
}
public async Task<bool> Accept(UserIdAttribute attr, string val)
{
fieldA = val;
return await Check();
}
public async Task<bool> Accept(TeamIdAttribute attr, string val)
{
fieldB = val;
return await Check();
}
}
other issue, if you guys already know how to solve it.
Im validating the request itself by headers and im storing some data in the actionContext's principal. In controllers i use: ActionContext.RequestContext.Principal.Identity.Name
is there any way to get this data when in validationAttribute scope?
Thanks.

Related

C# - Is it possible to create new local Guid variable, automatically whenver a method is being called?

I am looking to implement Guid trace mechanism for every method that is being called but I don't want to write this on every method:
Guid TraceId = Guid.NewGuid()
Is there an attribute or some other way that can automatically generate Guid whenever a method is being called and have it stored/available as local variable?
For MVC ApiControllers I used this logic, but I need it to happen for different class types as well.
[TraceActionFilter]
public class TracedApiController : System.Web.Http.ApiController
{
public TracedApiController()
{
}
public string TraceId
{
get
{
return ActionContext.ActionArguments[TraceActionFilterAttribute.TRACE_ID].ToString();
}
}
public Guid TraceIdGuid
{
get
{
return Guid.Parse(TraceId);
}
}
}
public class TraceActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ActionArguments.ContainsKey(TRACE_ID))
{
actionContext.ActionArguments.Add(TRACE_ID, Guid.NewGuid());
}
else actionContext.ActionArguments[TRACE_ID] = Guid.NewGuid();
}
public static string TRACE_ID = "TraceId";
}

Custom attributes not behaving like data annotations

I am trying to create a custom attribute in console application but it is not working. My custom attribute never gets called. I found a good example here Custom Attribute not being hit
but not happy with its implementation.
I am wondering how data annotations works in MVC. we don't have to call it separately.
Is MVC calling those data annotations attribute behind the scene?
I wish to create custom attribute that I can use it on any class property same like data annotations attribute. But calling it separately like in above link is not what i am looking.
Here is what I have tried:
using System;
namespace AttributePractice
{
[AttributeUsage(AttributeTargets.Property)]
public class CustomMessageAttribute : Attribute
{
public static readonly CustomMessageAttribute Default = new CustomMessageAttribute();
protected string Message { get; set; }
public CustomMessageAttribute() : this(string.Empty)
{
Console.WriteLine("Default message is empty");
}
public CustomMessageAttribute(string message)
{
Message = message;
}
public string MyMessage =>
Message;
public override bool Equals(object obj)
{
if (obj == this)
return true;
if (obj is CustomMessageAttribute customMessageAttribute)
return customMessageAttribute.Message == MyMessage;
return false;
}
public override int GetHashCode()
{
return MyMessage.GetHashCode();
}
public override bool IsDefaultAttribute()
{
return Equals(Default);
}
}
public class Person
{
//This never works
// I am looking to use this attribute anywhere without calling it
// separately , same like data annotations
[CustomMessage("Hello world")]
public string Name { get; set; }
public int Age { get; set; }
public void DisplayPerson()
{
Console.WriteLine(Name);
Console.WriteLine(Age);
}
}
internal static class Program
{
private static void Main(string[] args)
{
var personObj = new Person
{
Name = "Tom",
Age = 28
};
personObj.DisplayPerson();
}
}
}
Can anybody tell me how to make my custom attribute works like data annotation way?
yes, if you need 10 custom attributes, you should create 10 separate.

Encrypt/Decrypt property while writing/reading to c# mongo db

Just going to lay out all the info i have:
In short, I am looking for something exactly (literally) like this but compatible with ASP Core (2.2) and the C# MongoDB Driver (2.7).
This seems like such a common requirement, I am very surprised i can't find anything already built.
Here is what i have so far:
Model:
public class Patient
{
//comes from the client as XXXXXXXXX, RegEx: "([0-9]{9})"
//[MongoEncrypt]
public EncryptedString SocialSecurityNumber { get; set; }
}
Attribute:
[AttributeUsage(AttributeTargets.Property)]
public class MongoEncryptAttribute : BsonSerializerAttribute
{
public MongoEncryptAttribute()
{
SerializerType = typeof(MongoEncryptSerializer);
}
}
Custom Serializer:
public interface IMongoEncryptSerializer : IBsonSerializer<EncryptedString>{ }
public class MongoEncryptSerializer : SerializerBase<EncryptedString>, IMongoEncryptSerializer
{
private readonly string _encryptionKey;
public MongoEncryptSerializer(IConfiguration configuration)
{
_encryptionKey = configuration.GetSection("MongoDb")["EncryptionKey"];
}
public override EncryptedString Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var encryptedString = context.Reader.ReadString();
return AesThenHmac.SimpleDecryptWithPassword(encryptedString, _encryptionKey);
}
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, EncryptedString value)
{
var encryptedString = AesThenHmac.SimpleEncryptWithPassword(value, _encryptionKey);
context.Writer.WriteString(encryptedString);
}
}
Open Items:
Use DI (vanilla .net core DI) to get the Serializer. thinking of something like BsonSerializer.RegisterSerializer(type,serializer) in a bootstrap method where i can access the service collection and do a GetInstance but then i would need string SocialSecurityNumber to use a custom type (maybe SecureString?)
Went with a custom type,EncryptedString, with implicit string conversion
Use DI in the serializer to get the key (initially from IConfiguration/appsettings.json and then ultimately from Azure KeyVault (whole new can of worms for me)) and the EncryptionProvider
deterministic encryption for searching. AesThenHmac comes from this popular post. I can store and retrieve data back fine in its current implementation. But in order to search for SSNs, I need deterministic encryption which this lib does not provide.
My Solution:
Model:
public class Patient
{
//comes from the client as XXXXXXXXX, RegEx: "([0-9]{9})"
public EncryptedString SocialSecurityNumber { get; set; }
}
Custom Type:
public class EncryptedString
{
private readonly string _value;
public EncryptedString(string value)
{
_value = value;
}
public static implicit operator string(EncryptedString s)
{
return s._value;
}
public static implicit operator EncryptedString(string value)
{
if (value == null)
return null;
return new EncryptedString(value);
}
}
Serializer(using Deterministic Encryption):
public interface IEncryptedStringSerializer : IBsonSerializer<EncryptedString> {}
public class EncryptedStringSerializer : SerializerBase<EncryptedString>, IEncryptedStringSerializer
{
private readonly IDeterministicEncrypter _encrypter;
private readonly string _encryptionKey;
public EncryptedStringSerializer(IConfiguration configuration, IDeterministicEncrypter encrypter)
{
_encrypter = encrypter;
_encryptionKey = configuration.GetSection("MongoDb")["EncryptionKey"];
}
public override EncryptedString Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var encryptedString = context.Reader.ReadString();
return _encrypter.DecryptStringWithPassword(encryptedString, _encryptionKey);
}
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, EncryptedString value)
{
var encryptedString = _encrypter.EncryptStringWithPassword(value, _encryptionKey);
context.Writer.WriteString(encryptedString);
}
}
Registering the serializer:
collection.AddScoped<IEncryptedStringSerializer, EncryptedStringSerializer>();
//then later...
BsonSerializer.RegisterSerializer<EncryptedString>(sp.GetService<IEncryptedStringSerializer>());

How to get my typed result in a custom filter attribute?

This is my code:
// Controller
[HttpGet("{id}")]
[MyFilter]
public async Task<MyCustomType> Load(string id)
{
return new MyCustomType(....);
}
// Custom attribute
public class MyFilterAttribute : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
// Can I have my MyCustomType result here?
}
}
I need to implement some special logic in case of specific property values of MyCustomType result.
Public class MyCustomType
{
// assuming that there will be more properties
public int Id { get; set; }
public string Name { get; set; }
}
// Now, Move to Controller method.
public class CustomController : Controller
{
[HttpGet({"id"})]
[MyFilter]
public async Task<MyCustomType> Load(string id)
{
// Do some async operations
// Or do some Db queries
// returning MyCustomType
MyCustomType typeToReturn = new MyCustomType();
typeToReturn.Id = 1;
typeToReturn.Name = "something";
return typeToReturn;
}
}
// Well here goes the attribute
public class MyFilterAttribute : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
// you have to do more digging i am using dynamic to get the values and result.
dynamic content = context.Result;
if (content != null)
{
dynamic values = content.Value;
}
}
}
EDIT changed the code and ran it in a dot net core project and i was able to get the values, how ever i have used dynamic you can dig more on it.

Customizing Output Cache in MVC3

I have some controller actions that I want to have custom caching on. For example lets say I have a
controller action ActionResult Index(string name) {}. I want to cache on the server the HTML of this action, unless there is a "live=true" querystring parameter in the url. If that parameter is present I would like to remove that action result from the server cache and serve the response normally.
We use OutputCache(Location=OutputCacheLocation.Server) attribute to do our caching usually. Is it possible to extend this attribute somehow and make it clear the cache if the live=true parameter is present in the URL?
Are there alternative that I can use to accomplish this if I can't customize the OutputCache attribute to get the behavior I need?
UPDATE
Based on James feedback here is the code that I have:
public class LiveOutputCacheAttribute : OutputCacheAttribute
{
private const string _resetParam = "live";
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var context = filterContext.HttpContext;
AddLiveToVaryByParam();
if (context.Request[_resetParam] == "true")
{
var urlToRemove = GetUrlToRemove(filterContext);
context.Response.RemoveOutputCacheItem(urlToRemove);
return;
}
base.OnActionExecuting(filterContext);
}
private void AddLiveToVaryByParam()
{
// add live reset flag when vary by param is specified
if (VaryByParam != "*" && !VaryByParam.Contains("live"))
VaryByParam = string.Format("{0};{1}",VaryByParam, _resetParam).TrimStart(';');
}
private static string GetUrlToRemove(ActionExecutingContext filterContext)
{
var routeValues = new RouteValueDictionary(filterContext.ActionParameters);
var urlHelper = new UrlHelper(filterContext.RequestContext);
string action = filterContext.ActionDescriptor.ActionName;
string controller = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
return urlHelper.Action(action, controller, routeValues);
}
}
Here is how I use this on my action:
[LiveOutputCache(Location = OutputCacheLocation.Server, Duration = 60 * 60, VaryByParam = "name")]
public ActionResult Index(string name)
{
ViewData.Model = name + "-----" + DateTime.Now.Ticks.ToString();
return View();
}
The problem is that when I use the live=true parameter, it is still not removing the original request from the cache. Am I doing something wrong here?
You could use the VaryByParam attribute to check whether the live option is true e.g.
public class LiveOutputCacheAttribute : OutputCacheAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (VaryByParam == "true")
{
// clear cache
return;
}
base.OnActionExecuting(filterContext);
}
}
...
[LiveOutputCache(Location=OutputCacheLocation.Server, VaryByParam="live")]
public ActionResult Index(string name)
{
...
}
See How to programmatically clear outputcache for controller action method for the clearing part of it.
You can't customize OutputCacheAttribute to get the behaviour, but you could write your own CustomCacheAttribute to achive this. To do this you can get sources of OutputCacheAttribute (MVC is opensource so you can do it), copy it and rewrite function OnActionExecuting(ActionExecutingContext filterContext).
Check out my blog post on creating your own Custom Output Cache in ASP.NET MVC http://bstavroulakis.com/blog/web/custom-output-caching-in-asp-net-mvc/
I had the following expectations from the output cache which weren't met
1) Have the ability to view the Caching Object when necessary and all of its children to invalidate parts when needed.
2) Have the ability to disable caching when needed.
3) Have some logic before and after the item was cached.
4) Have some parts dynamic on the site and load only those parts, having the rest of the site static
5) Use the cache structure in other parts of the site as well.
My actions where:
To create my own CacheManager that will add/remove/find/... objects
in the cache.
public class CacheManager
{
#region ICacheManager Members
public static void Add(string key, object value, int expireSeconds)
{
if (expireSeconds == CacheManagerKey.CacheLifeSpanForever)
WebCache.Add(key, value, null, System.Web.Caching.Cache.NoAbsoluteExpiration, System.Web.Caching.Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
else
WebCache.Add(key, value, null, DateTime.MaxValue, TimeSpan.FromSeconds(expireSeconds), CacheItemPriority.Normal, null);
}
public static bool Contains(string key)
{
return WebCache.Get(key) != null;
}
public static int Count()
{
return WebCache.Count;
}
public static void Insert(string key, object value)
{
WebCache.Insert(key, value);
}
public static T Get(string key)
{
return (T)WebCache.Get(key);
}
public static List GetCacheKeys()
{
List keys = new List();
foreach (DictionaryEntry entry in HttpContext.Current.Cache) keys.Add(entry.Key.ToString());
return keys;
}
public static void Remove(string key)
{
WebCache.Remove(key);
}
public static void RemoveAll()
{
List keys = GetCacheKeys();
foreach (string key in keys)
WebCache.Remove(key);
}
public object this[string key]
{
get
{
return WebCache[key];
}
set
{
WebCache[key] = value;
}
}
#endregion
public static System.Web.Caching.Cache WebCache
{
get
{
System.Web.Caching.Cache cache = null;
if (HttpContext.Current != null)
cache = HttpContext.Current.Cache;
if (cache == null)
cache = HttpRuntime.Cache;
return cache;
}
}
}
After that I created my own attribute
[AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = false)]
public class WebCacheAttribute : ActionFilterAttribute
{
public int Duration { get; set; }
public string CacheKey { get; set; }
public Dictionary CacheParams { get; set; }
public Type CacheReturnType { get; set; }
public string ContentType { get; set; }
public HeaderContentTypeEnum ResponseHeaderContentType{get;set;}
public string CacheObj { get; set; }
private readonly ICacheHoleFiller _cacheHoleFiller;
public WebCacheAttribute(int duration, string cacheKey, string cacheParamsStr, HeaderContentTypeEnum response = HeaderContentTypeEnum.Html, Type type = null)
{
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
}
public T GetCachedParam(Dictionary parameters, bool isAjaxRequest)
{
}
public string GetUniqueKey(bool isAjaxRequest)
{
}
public void OnException(ExceptionContext filterContext)
{
}
private HtmlTextWriter tw;
private StringWriter sw;
private StringBuilder sb;
private HttpWriter output;
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
}
}
To have some parts dynamic I used "Donut Caching"
Lastly, I created a cachehelper to call other methods in my project that will use the webcache attribute as well.
var articleStr = CacheHelper.InvokeCacheMethod(typeof(HtmlHelperExtensions), "RenderArticlesCallback", new object[] { (int)articleType });
[WebCacheAttribute(CacheManagerKey.CacheLifeSpanForever, CacheManagerKey.Page_Article_Key, "articleTypeID")]
public static string RenderArticlesCallback(int articleTypeID)
{
public static class CacheHelper
{
public delegate object SourceDataDelegate(object[] args);
public static T InvokeCacheMethod(Type type, string methodName, object[] args)
{
return (T)InvokeCacheMethod(type, methodName, null, args);
}
public static T InvokeCacheMethod(Type type, string methodName, object instance, object[] args)
{
var method = type.GetMethod(methodName);
var webCache = method.ReturnParameter.Member.GetCustomAttributes(typeof(WebCacheAttribute), true).FirstOrDefault();
Dictionary cacheParameters = FixCacheParameters(method, args);
T cachedObj;
if (Config.CacheEnabled && webCache != null)
{
cachedObj = ((WebCacheAttribute)webCache).GetCachedParam(cacheParameters, false);
if (cachedObj != null)
return cachedObj;
}
T returnObj = (T)method.Invoke(instance, args);
SaveCachedData(webCache, returnObj);
return returnObj;
}
public static void SaveCachedData(object webCache, object returnObj)
{
if (Config.CacheEnabled && webCache != null)
{
var fullParamString = ((WebCacheAttribute)webCache).GetUniqueKey(false);
CacheManager.Add(fullParamString, returnObj, ((WebCacheAttribute)webCache).Duration);
}
}
public static Dictionary FixCacheParameters(MethodInfo method, object[] args)
{
Dictionary cacheParameters = new Dictionary();
if (args != null)
{
var arguments = args.ToList();
var count = 0;
var argsCount = args.Length;
var methodParameters = method.GetParameters().ToList();
foreach (var argument in args)
{
var key = methodParameters[count].Name;
object value = null;
if (argsCount > count)
value = args[count];
if (value != null && value.GetType() == typeof(string))
value = (object)value.ToString();
if (value != null)
cacheParameters.Add(key, value);
count++;
}
}
return cacheParameters;
}
}
For more details on all of this you can visit my blog post => Custom Output Caching in ASP.NET MVC

Categories

Resources