What is the recommended way to doing nhibernate session management in asp.net-mvc that supports second level cache / transactions, etc? - session

I am struggling to get second level caching and transactions working in my asp.net-mvc site and I am thinking it has to do with how I have my session managements setup.
Basically I have the following classes:
NhibernateRepository
SessionManager
and I am using Unity IOC Container:
this.RegisterType<IRepository, NHibernateRepository>(new PerResolveLifetimeManager());
this.RegisterType<ISessionManager, SessionManager>(new PerResolveLifetimeManager());
The NhibernateRepository class looks like this with a Session property
public NHibernateRepository(UserModel userModel, ISessionManager sessionManager)
{
UserModel = userModel;
SessionManager = sessionManager;
}
public ISession Session
{
get
{
using (_lock.WaitToRead())
{
if (_session != null) return _session;
}
using (_lock.WaitToWrite())
{
if (_session != null) return _session;
_session = SessionManager.GetSession(UserModel == null ? "Task" : UserModel.FullName);
return _session;
}
}
}
The session Manager class looks like this:
public class SessionManager : ISessionManager
{
private static readonly ResourceLock _lock = new OneManyResourceLock();
public static ISessionFactory Factory { get; set; }
public ISession GetSession(string userName)
{
ISession session = GetSessionFactory().OpenSession(new AuditInterceptor(userName));
return session;
}
private static ISessionFactory GetSessionFactory()
{
using (_lock.WaitToRead())
{
if (Factory != null) return Factory;
}
using (_lock.WaitToWrite())
{
if (Factory != null) return Factory;
string connectionString = ConfigurationManager.ConnectionStrings["DomainConnection"].ConnectionString;
Factory = FluentlyConfigureFactory(connectionString, false);
return Factory;
}
}
private static ISessionFactory FluentlyConfigureFactory(string connectionString, bool showSql)
{
MsSqlConfiguration databaseConfiguration = MsSqlConfiguration.MsSql2005
.ConnectionString(c => c.Is(connectionString))
.Dialect<SparcMsSqlDialect>()
.UseOuterJoin()
.UseReflectionOptimizer();
if (showSql)
{
databaseConfiguration.ShowSql();
}
databaseConfiguration.Raw("generate_statistics", showSql.ToString());
FluentConfiguration configuration = Fluently.Configure().Database(databaseConfiguration);
return configuration
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<ApplicationMap>().Conventions.Add(typeof(Conventions)))
.ExposeConfiguration(
c => {
c.SetProperty("cache.provider_class", "NHibernate.Caches.SysCache.SysCacheProvider, NHibernate.Caches.SysCache");
c.SetProperty("cache.use_second_level_cache", "true");
c.SetProperty("cache.use_query_cache", "true");
c.SetProperty("expiration", "86400");
})
.BuildSessionFactory();
}
Does anyone see anything fundamentally wrong with this? From googling I see all different opinions on how you should setup asp.net-mvc with nhibernate (adding transaction in beginRequest and committing on endRequest, etc) but I can't find the canonical way of getting it working with second level caching, etc that seems to be the best practice for having high scalability, etc
I tried adding transactions into this code given what I have read but i now seem to be getting this error:
Initializing[ (one of my domain objects) #1]-Could not initialize proxy - no Session.
so I reverted that code. Basically I am hoping there is a "go to" best practice at this point for using any second level cache, transactions in asp.net-mvc . .

I like to use an implementation of memory cache class which practically handles the object locks for you, I've implemented a custom module for a 2 level cache for nHibernate and you can plug by doing some small configuration, for that you need to implement is ICache interface.
public class NHibernateCache2 : ICache
{
private static readonly IInternalLogger Log = LoggerProvider.LoggerFor(typeof(NHibernateCache2));
private readonly string _region;
private string _regionPrefix;
private readonly MemoryCache _cache;
private TimeSpan _expiration;
private CacheItemPriority _priority;
// The name of the cache key used to clear the cache. All cached items depend on this key.
private readonly string _rootCacheKey;
private bool _rootCacheKeyStored;
private static readonly TimeSpan DefaultExpiration = TimeSpan.FromSeconds(300);
private static readonly string DefauktRegionPrefix = string.Empty;
private const string CacheKeyPrefix = "NHibernate-Cache:";
public NHibernateCache2():this("nhibernate", null)
{
}
public NHibernateCache2(string region):this(region, null)
{
}
/// There are two (2) configurable parameters:
/// expiration = number of seconds to wait before expiring each item
/// priority = a numeric cost of expiring each item, where 1 is a low cost, 5 is the highest, and 3 is normal. Only values 1 through 5 are valid.
/// All parameters are optional. The defaults are an expiration of 300 seconds and the default priority of 3.
public NHibernateCache2(string region, IDictionary<string, string> properties)
{
_region = region;
_cache = MemoryCache.Default;
Configure(properties);
_rootCacheKey = GenerateRootCacheKey();
StoreRootCacheKey();
}
/// Defines property in order to get the region for the NHibernate's Cache.
public string Region
{
get { return _region; }
}
/// Obtains a expiration value that indicates the time in seconds after which an object is automatically
/// evicted from the cache.
public TimeSpan Expiration
{
get { return _expiration; }
}
/// Obtains a priority value that indicates the likelihood that an object of that region evicts
/// another already cached object of a lower priority region.
public CacheItemPriority Priority
{
get { return _priority; }
}
private void Configure(IDictionary<string, string> props)
{
if (props == null)
{
if (Log.IsWarnEnabled)
{
Log.Warn("configuring cache with default values");
}
_expiration = DefaultExpiration;
_priority = CacheItemPriority.Default;
_regionPrefix = DefauktRegionPrefix;
}
else
{
_priority = GetPriority(props);
_expiration = GetExpiration(props);
_regionPrefix = GetRegionPrefix(props);
}
}
private static string GetRegionPrefix(IDictionary<string, string> props)
{
string result;
if (props.TryGetValue("regionPrefix", out result))
{
Log.DebugFormat("new regionPrefix :{0}", result);
}
else
{
result = DefauktRegionPrefix;
Log.Debug("no regionPrefix value given, using defaults");
}
return result;
}
private static TimeSpan GetExpiration(IDictionary<string, string> props)
{
TimeSpan result = DefaultExpiration;
string expirationString;
if (!props.TryGetValue("expiration", out expirationString))
{
props.TryGetValue(NHibernate.Cfg.Environment.CacheDefaultExpiration, out expirationString);
}
if (expirationString != null)
{
try
{
int seconds = Convert.ToInt32(expirationString);
result = TimeSpan.FromSeconds(seconds);
Log.Debug("new expiration value: " + seconds);
}
catch (Exception ex)
{
Log.Error("error parsing expiration value");
throw new ArgumentException("could not parse 'expiration' as a number of seconds", ex);
}
}
else
{
if (Log.IsDebugEnabled)
{
Log.Debug("no expiration value given, using defaults");
}
}
return result;
}
private static CacheItemPriority GetPriority(IDictionary<string, string> props)
{
CacheItemPriority result = CacheItemPriority.Default;
string priorityString;
if (props.TryGetValue("priority", out priorityString))
{
result = ConvertCacheItemPriorityFromXmlString(priorityString);
if (Log.IsDebugEnabled)
{
Log.Debug("new priority: " + result);
}
}
return result;
}
private static CacheItemPriority ConvertCacheItemPriorityFromXmlString(string priorityString)
{
if (string.IsNullOrEmpty(priorityString))
{
return CacheItemPriority.Default;
}
var ps = priorityString.Trim().ToLowerInvariant();
if (ps.Length == 1 && char.IsDigit(priorityString, 0))
{
// the priority is specified as a number
int priorityAsInt = int.Parse(ps);
if (priorityAsInt >= 1 && priorityAsInt <= 6)
{
return (CacheItemPriority)priorityAsInt;
}
}
else
{
/// change for your own priority settings
switch (ps)
{
case "abovenormal":
return CacheItemPriority.Default;
case "belownormal":
return CacheItemPriority.Default;
case "default":
return CacheItemPriority.Default;
case "high":
return CacheItemPriority.Default;
case "low":
return CacheItemPriority.Default;
case "normal":
return CacheItemPriority.Default;
case "notremovable":
return CacheItemPriority.NotRemovable;
}
}
Log.Error("priority value out of range: " + priorityString);
throw new IndexOutOfRangeException("Priority must be a valid System.Web.Caching.CacheItemPriority; was: " + priorityString);
}
private string GetCacheKey(object key)
{
return String.Concat(CacheKeyPrefix, _regionPrefix, _region, ":", key.ToString(), "#", key.GetHashCode());
}
/// Gets an object that exist in the second level cache of NHibernate by the specified key.
///A unique identifier for the cache entry to get.
///Returns an entry from the NHibernate's Cache.
public object Get(object key)
{
if (key == null)
{
return null;
}
string cacheKey = GetCacheKey(key);
if (Log.IsDebugEnabled)
{
Log.Debug(String.Format("Fetching object '{0}' from the cache.", cacheKey));
}
object obj = _cache.Get(cacheKey);
if (obj == null)
{
return null;
}
var de = (DictionaryEntry)obj;
if (key.Equals(de.Key))
{
return de.Value;
}
else
{
return null;
}
}
/// Adds a specific object inside the in the second level cache of NHibernate by using its key and its content.
/// A key value of an item from the second level cache of NHibernate.
/// Data for an entry of second level cache of NHibernate.
public void Put(object key, object value)
{
if (key == null)
{
throw new ArgumentNullException("key", "null key not allowed");
}
if (value == null)
{
throw new ArgumentNullException("value", "null value not allowed");
}
string cacheKey = GetCacheKey(key);
if (_cache[cacheKey] != null)
{
if (Log.IsDebugEnabled)
{
Log.Debug(String.Format("updating value of key '{0}' to '{1}'.", cacheKey, value));
}
// Remove the key to re-add it again below
_cache.Remove(cacheKey);
}
else
{
if (Log.IsDebugEnabled)
{
Log.Debug(String.Format("adding new data: key={0}&value={1}", cacheKey, value));
}
}
if (!_rootCacheKeyStored)
{
StoreRootCacheKey();
}
var cacheItemPolicy = new CacheItemPolicy()
{
AbsoluteExpiration = DateTime.Now.Add(_expiration),
SlidingExpiration = ObjectCache.NoSlidingExpiration,
Priority = _priority,
};
cacheItemPolicy.ChangeMonitors.Add(_cache.CreateCacheEntryChangeMonitor(new[] { _rootCacheKey }));
_cache.Add(
cacheKey,
new DictionaryEntry(key, value),
cacheItemPolicy);
}
/// Removes a cache entry from second level cache of NHibernate by a key.
/// A key value of an item from second level cache of NHibernate.
public void Remove(object key)
{
if (key == null)
{
throw new ArgumentNullException("key");
}
string cacheKey = GetCacheKey(key);
if (Log.IsDebugEnabled)
{
Log.Debug("removing item with key: " + cacheKey);
}
_cache.Remove(cacheKey);
}
/// Removes an object/item from second level cache of NHibernate.
public void Clear()
{
RemoveRootCacheKey();
StoreRootCacheKey();
}
/// Generate a unique root key for all cache items to be dependant upon
private string GenerateRootCacheKey()
{
return GetCacheKey(Guid.NewGuid());
}
private void RootCacheItemRemoved(CacheEntryRemovedArguments arguments)
{
_rootCacheKeyStored = false;
}
private void StoreRootCacheKey()
{
_rootCacheKeyStored = true;
var policy = new CacheItemPolicy
{
AbsoluteExpiration = ObjectCache.InfiniteAbsoluteExpiration,
SlidingExpiration = ObjectCache.NoSlidingExpiration,
Priority = CacheItemPriority.Default,
RemovedCallback = RootCacheItemRemoved
};
_cache.Add(
_rootCacheKey,
_rootCacheKey,
policy);
}
private void RemoveRootCacheKey()
{
_cache.Remove(_rootCacheKey);
}
/// Clears the second level cache of NHibernate.
public void Destroy()
{
Clear();
}
public void Lock(object key)
{
// Do nothing
}
public void Unlock(object key)
{
// Do nothing
}
/// Obtains the next timestamp value.
public long NextTimestamp()
{
return Timestamper.Next();
}
/// Defines property in order to get the sliding expiration time for the second level cache of NHibernate.
public int Timeout
{
get { return Timestamper.OneMs * 60000; } // 60 seconds
}
/// Retrieves the name of NHibernate second level cache region.
public string RegionName
{
get { return _region; }
}
}
then you need to define an ICacheProvider implementation:
public class NHibernateCacheProvider2 : ICacheProvider
{
private static readonly Dictionary<string, ICache> Caches;
private static readonly IInternalLogger Log;
static NHibernateCacheProvider2()
{
Log = LoggerProvider.LoggerFor(typeof(NHibernateCacheProvider2));
Caches = new Dictionary<string, ICache>();
}
/// Builds a new SysCache through the region and a collection of properties.
/// regionName: The name of the cache region.
/// properties: Configuration settings.
/// returns A new instance of NHibernateCache by using a region of the cache.
public ICache BuildCache(string regionName, IDictionary<string, string> properties)
{
if (regionName == null)
{
regionName = string.Empty;
}
ICache result;
if (Caches.TryGetValue(regionName, out result))
{
return result;
}
// create cache
if (properties == null)
{
properties = new Dictionary<string, string>(1);
}
if (Log.IsDebugEnabled)
{
var sb = new StringBuilder();
sb.Append("building cache with region: ").Append(regionName).Append(", properties: ");
foreach (KeyValuePair<string, string> de in properties)
{
sb.Append("name=");
sb.Append(de.Key);
sb.Append("&value=");
sb.Append(de.Value);
sb.Append(";");
}
Log.Debug(sb.ToString());
}
return new NHibernateCache2(regionName, properties);
}
public long NextTimestamp()
{
return Timestamper.Next();
}
public void Start(IDictionary<string, string> properties) { //your impl it's not necessary }
public void Stop() { }
}
if you are using fluent nhibernate you can register it with the following configuration:
Fluently.Configure().Database(MsSqlConfiguration.MsSql2008.
ConnectionString(builder => builder.FromConnectionStringWithKey(connectionStringKey)))
.ExposeConfiguration(c =>{c.SetProperty("show_sql", "true");}).
Cache(builder =>builder.ProviderClass<NHibernateCacheProvider2().
UseSecondLevelCache().UseQueryCache())
I hope that helps

Related

How to avoid cache misses with memory cache in .net-core-2.1

How is it possible to update the cache in the background to avoid cache misses?
In .net-core-2.1 I can add a memory cache like so:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMemoryCache();
}
}
Then it's very straightforward to use:
[Route("api")]
public class DataController : Controller
{
private readonly IMemoryCache _cache;
private readonly DataContext _dataContext;
public DataController(IMemoryCache cache, DataContext dataContext)
{
_cache = cache;
_dataContext = dataContext;
}
[HttpGet]
[Route("GimmeCachedData")]
public async Task<IActionResult> Get()
{
var cacheEntry = await
_cache.GetOrCreateAsync("MyCacheKey", entry =>
{
entry.AbsoluteExpiration = DateTime.Now.AddSeconds(20);
return Task.FromResult(_dataContext.GetOrders(DateTime.Now));
});
return Ok(cacheEntry);
}
}
However, after 20 seconds of amazingly fast cached powered bliss infused requests, as expected, the cached item is expired and the next request is stalled because of a cache-miss, and subsequent data loading.
Argh! so the cache only works sometimes. Why not have the option of having it work all the time?
How can I add functionality to:
return the old item (in the meantime) AND
automatically update the cache when items expire or are noticed to be expired so the next request will get the updated value?
In trying to solve this problem I have encountered 2 main obstacles with my implementation using an IHostedService:
When the cached item is expired it's evicted and no longer available; meaning I can't return it.
Updating cached items that require the database cause those calls to happen out of scope.
This cache update can kick off either directly after noticing a cache miss, or by actively monitoring for the next item to expire.
I've tried rolling my own cache (adding it as a singleton) using a ConcurrentDictionary<String, CacheItem>. The CacheItem class contains properties for the Value, Expiration, and a Factory (i.e.: a value-returning-delegate). But I found, as this delegate is probably set at request time and called in the IHostedService background thread, it caused context out of scope exception.
I have found a solution that seems to work.
Implement an IHostedService (extended from BackgroundService class). This class will serve as the background thread managed by the .net core framework. The background thread will keep the cache updates going (by calling ICache.UpdateCache as explained below), to avoid request-time cache misses.
public class CacheUpdateService : BackgroundService
{
private readonly ILogger<CacheUpdateService> _logger;
private readonly IServiceProvider _serviceProvider;
private readonly ICache _cache;
public CacheUpdateService(ILogger<CacheUpdateService> logger, IServiceProvider serviceProvider, ICache cache)
{
_logger = logger;
_serviceProvider = serviceProvider;
_cache = cache;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogDebug("CacheUpdateService is starting.");
stoppingToken.Register(Dispose);
while (!stoppingToken.IsCancellationRequested)
{
try
{
using (var scope = _serviceProvider.CreateScope())
{
var dataContext = scope.ServiceProvider.GetRequiredService<DataContext>();
// This tight loop calls the UpdateCache, which will block if no updates are necessary
await Task.Run(() => _cache.UpdateCache(dataContext), stoppingToken);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception in the CacheUpdateService");
}
}
_logger.LogDebug("CacheUpdateService has stopped.");
}
public override void Dispose()
{
using(var scope = _serviceProvider.CreateScope())
{
var scopedProcessingService = scope.ServiceProvider.GetRequiredService<ICache>();
// Dispose here on ICache will release any blocks
scopedProcessingService.Dispose();
}
base.Dispose();
}
}
The Cache class below implements the background UpdateCache method which will update 1 expired item at a time. Prioritizing the one most expired. It also implements request-scoped GetOrCreate method. Note I'm using a delegate (Func<IDataContext, Object>) in the CacheEntry as the value population factory. This allows Cache class to inject a properly scoped DataContext (received from the IHostedService) and it also allows the caller to specify which method of the DataContext is called to get the results of the specific cache key value. Notice I'm using an AutoResetEvent to wait for 1st-time data population as well as a timer to kick off the next cache refresh. This implementation will suffer a cache-miss for the 1st time the item is called (and I guess after it hasn't been called for more than 1 hour; as it will be evicted after 1 hr.).
public class CacheEntry
{
public String Key { get; set; }
public Object Value { get; set; }
public Boolean Updating { get; set; }
public Int32 ExpirySeconds { get; set; }
public DateTime Expiration { get; set; }
public DateTime LastAccessed { get; set; }
public Func<IDataContext, Object> ValueFactory { get; set; }
}
public interface ICache : IDisposable
{
void UpdateCache(IDataContext dataContext);
T GetOrCreate<T>(String key, Func<IDataContext, T> factory, Int32 expirySeconds = 0) where T : class;
}
public class Cache : ICache
{
private readonly ILogger _logger;
private readonly ConcurrentDictionary<String, CacheEntry> _cache;
private readonly AutoResetEvent _governor;
public Cache(ILogger<Cache> logger)
{
_logger = logger;
_cache = new ConcurrentDictionary<String, CacheEntry>();
_governor = new AutoResetEvent(false);
}
public void Dispose()
{
_governor.Set();
}
public static Int32 CacheForHour => 3600;
public static Int32 CacheForDay => 86400;
public static Int32 CacheIndefinitely => 0;
public void UpdateCache(IDataContext dataContext)
{
var evictees = _cache.Values
.Where(entry => entry.LastAccessed.AddHours(1) < DateTime.Now)
.Select(entry => entry.Key)
.ToList();
foreach (var evictee in evictees)
{
_logger.LogDebug($"Evicting: {evictee}...");
_cache.Remove(evictee, out _);
}
var earliest = _cache.Values
.Where(entry => !entry.Updating)
.OrderBy(entry => entry.Expiration)
.FirstOrDefault();
if (earliest == null || earliest.Expiration > DateTime.Now)
{
var timeout = (Int32) (earliest?.Expiration.Subtract(DateTime.Now).TotalMilliseconds ?? -1);
_logger.LogDebug($"Waiting {timeout}ms for next expiry...");
_governor.WaitOne(timeout);
return;
}
try
{
_logger.LogDebug($"Updating cache for: {earliest.Key}...");
earliest.Updating = true;
earliest.Value = earliest.ValueFactory(dataContext);
earliest.Expiration = earliest.ExpirySeconds > 0
? DateTime.Now.AddSeconds(earliest.ExpirySeconds)
: DateTime.MaxValue;
_governor.Set();
}
finally
{
earliest.Updating = false;
}
}
public T GetOrCreate<T>(String key, Func<IDataContext, T> factory, Int32 expirySeconds = -1) where T : class
{
var success = _cache.TryGetValue(key, out var entry);
if (success && entry.Value != null)
{
entry.LastAccessed = DateTime.Now;
return (T) entry.Value;
}
if (entry == null)
{
_logger.LogDebug($"Adding new entry to the cache: {key}...");
entry = new CacheEntry
{
Key = key,
Expiration = DateTime.MinValue,
ExpirySeconds = expirySeconds,
LastAccessed = DateTime.Now,
ValueFactory = factory
};
_cache.TryAdd(key, entry);
_governor.Set();
}
while (entry.Value == null)
{
_logger.LogDebug($"Waiting for 1st time cache update: {entry.Key}...");
_governor.WaitOne();
}
return (T)entry.Value;
}
}
The DataContext class is then created like so. Using Dapper for example to retrieve the data from the database:
public class DataContext : DbContext, IDataContext
{
private readonly IOptions<Settings> _settings;
private String _databaseServer;
public DataContext(IOptions<Settings> settings)
{
_settings = settings;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlServer(_settings.Value.ConnectionString);
}
public IEnumerable<OrderInfo> GetOrders(DateTime date)
{
return Database.GetDbConnection().Query<OrderInfo>(
$"SchemaName.usp_GetOrders",
new {Date = date},
commandType: CommandType.StoredProcedure);
}
}
In the controllers the ICache is injected and used as follows:
[HttpGet]
[Route("Orders/{date}")]
public IActionResult GetOrders(DateTime date)
{
var result = _cache.GetOrCreate(
$"GetOrders_{date:yyyyMMdd}",
context => context.GetOrders(date),
date.Date < DateTime.Today ? Cache.CacheIndefinitely : 20);
return Ok(result);
}
Finally register the classes as a Singleton in the DI setup
services.AddOptions();
services.Configure<Settings>(Configuration);
services.AddLogging();
services.AddDbContext<DataContext>();
services.AddSingleton<ICache, Cache>();
services.AddSingleton<IHostedService, CacheUpdateService>();
I created a similar IHostedService based project https://github.com/dpatekar/CacheAnt
Take a look, it is very simple to use.
It is available as a NuGet package also.

What is happening internally for auto increment field in data base side and application side?

I have a bean for spring application
#Entity
#Table(name="tbl_apply_leave")
public class ApplyLeaveModel{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int dbid;
private String employee_name;
public int getDbid() {
return dbid;
}
public void setDbid(int dbid) {
this.dbid = dbid;
}
public String getEmployee_name() {
return employee_name;
}
public void setEmployee_name(String employee_name) {
this.employee_name = employee_name;
}
}
then In controller before save this object value of dbid is null.
but after save the object using hibernate dbid contains value.
I don't know what's happening.
controller code is:
System.out.println(applyLeaveModel.getDbid()); // null
leave_dao.saveApplyLeaveModel(applyLeaveModel);
System.out.println(applyLeaveModel.getDbid()); // 5
public void saveApplyLeaveModel(ApplyLeaveModel applyLeaveModel) {
Session session = null;
Transaction trans_obj = null;
try {
session = sessionFactory.openSession();
if (session.isOpen() && session != null) {
trans_obj = session.beginTransaction();
session.persist(applyLeaveModel);
}
} catch (Exception e) {
System.out.println("save ApplyLeaveModel session " + e);
} finally {
trans_obj.commit();
session.close();
}
}
Hibernate executes PreparedStatement.getGeneratedKeys() to obtain the generated id after insert.
Also related: hibernate.jdbc.use_get_generated_keys.
Sidenote: Primitive types in Java cannot have null value. Probably you mean 0, or type of the id is Integer.

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>();
});

Setting session variables in a class is setting all of them, not just one

I have been researching a way to set session variables for my Web App to store the logged in user's info. I have made a lot of progress, being a novice, but I am stumped as to why setting the variables will set all of them and not just the one specified. Can someone point out my mistake(s), please? Thank you!!
using System;
using System.DirectoryServices;
using System.Security.Principal;
using System.Web;
using System.Web.UI;
namespace ITHD
{
public class Common : Page
{
public static void GetLoggedInUserProperties()
{
string gLoginId = ExtractUserName(WindowsIdentity.GetCurrent().Name);
SessionManager.LoginId = gLoginId;
VerifyInAD(gLoginId);
}
private static void VerifyInAD(string sUser)
{
try
{
string userName = ExtractUserName(sUser);
DirectorySearcher search = new DirectorySearcher();
search.Filter = String.Format("(SAMAccountName={0})", userName);
search.PropertiesToLoad.Add("cn");
search.PropertiesToLoad.Add("MemberOf");
search.PropertiesToLoad.Add("givenname");
search.PropertiesToLoad.Add("sn");
search.PropertiesToLoad.Add("phone");
search.PropertiesToLoad.Add("title");
SearchResult result = search.FindOne();
//SessionManager.CanEdit = "False";
SessionManager.UserName = string.Empty;
if (result != null)
{
SessionManager.UserName = string.Format("{0}", result.Properties["cn"][0].ToString());
bool admin = checkGroup("Helpdesk Agents");
if (admin == true)
{
SessionManager.HDAgent = "True";
}
}
search.Dispose();
}
catch (Exception ex)
{
SessionManager.UserName = "Guest";
}
}
public static string ExtractUserName(string path)
{
string[] userPath = path.Split(new char[] { '\\' });
return userPath[userPath.Length - 1];
}
public static bool checkGroup(string group)
{
WindowsIdentity identity = WindowsIdentity.GetCurrent();
WindowsPrincipal principal = new WindowsPrincipal(identity);
return principal.IsInRole(group);
}
}
public static class SessionManager
{
private const string sLoginId = "";
public static string LoginId
{
get
{
if (null != HttpContext.Current.Session[sLoginId])
return HttpContext.Current.Session[sLoginId] as string;
else
return "Guest";
}
set
{
HttpContext.Current.Session[sLoginId] = value;
}
}
private const string sUserName = "";
public static string UserName
{
get
{
if (null != HttpContext.Current.Session[sUserName])
return HttpContext.Current.Session[sUserName] as string;
else
return "Guest";
}
set
{
HttpContext.Current.Session[sUserName] = value;
}
}
private const string sHDAgent = "";
public static string HDAgent
{
get
{
if (null != HttpContext.Current.Session[sHDAgent])
return HttpContext.Current.Session[sHDAgent] as string;
else
return "False";
}
set
{
HttpContext.Current.Session[sHDAgent] = value;
}
}
}
}
I don't think you are setting your session keys correctly. In your static class you have a property setter that is essentially session Session[""]=Some Value for all members. You do not need to set the key to the name of the private member in .net 3.5 you do not even need the private member. You could also just stuff the whole session object in and not worry about each member.
Then just access a Current instantiator of the properties as in
MySession.Current.UserName="guest";
public class MySession
{
private const string _SessionName = "__MY_SESSION__";
//--------------------------------------------------------------------------------------------
private MySession(){}
//--------------------------------------------------------------------------------------------
public static MySession Current
{
get
{
MySession session =
(MySession)HttpContext.Current.Session[_SessionName];
if (session == null)
{
session = new MySession();
session.Property1 = new Property1();
session.Property2 = new Property2();
session.UserName = "";
HttpContext.Current.Session[_SessionName] = session;
}
return session;
}
}
public string UserName { get; set; }
public Property1 Property1 { get; set; }
public Property2 Property2 { get; set; }
}

data mode : read write with c# local database in wp7

I created a local db with helper app project. and deployed it from isolate storage to installation folder,i added to project directory with content build action by add existing item. my problem is that i want to insert data, but i don't know how to move the db file to isolate storage to insert and data must add to my .sdf file that is locate in my project directory also.
Souphia,
While learning to use WP, I wrote a simple application that tracked tasks.
One version of that app stored all task data in Sql on the phone.
You can read the post and download all the code for the app here:
http://www.ritzcovan.com/2012/02/building-a-simple-windows-phone-app-part-3/
But, here is some of the code from that project:
First we have the model class decorated with the appropriate attributes:
[Table]
public class Task : INotifyPropertyChanged, INotifyPropertyChanging
{
[Column(IsDbGenerated = false, IsPrimaryKey = true, CanBeNull = false)]
public string Id
{
get { return _id; }
set
{
NotifyPropertyChanging("Id");
_id = value;
NotifyPropertyChanging("Id");
}
}
[Column]
public string Name
{
get { return _name; }
set
{
NotifyPropertyChanging("Name");
_name = value;
NotifyPropertyChanged("Name");
}
}
[Column]
public string Category
{
get { return _category; }
set
{
NotifyPropertyChanging("Category");
_category = value;
NotifyPropertyChanged("Category");
}
}
[Column]
public DateTime? DueDate
{
get { return _dueDate; }
set
{
NotifyPropertyChanging("DueDate");
_dueDate = value;
NotifyPropertyChanged("DueDate");
}
}
[Column]
public DateTime? CreateDate
{
get { return _createDate; }
set
{
NotifyPropertyChanging("CreateDate");
_createDate = value;
NotifyPropertyChanged("CreateDate");
}
}
[Column]
public bool IsComplete
{
get { return _isComplete; }
set
{
NotifyPropertyChanging("IsComplete");
_isComplete = value;
NotifyPropertyChanged("IsComplete");
}
}
[Column(IsVersion = true)] private Binary _version;
private string _id;
private bool _isComplete;
private DateTime? _createDate;
private DateTime? _dueDate;
private string _name;
private string _category;
public event PropertyChangedEventHandler PropertyChanged;
public event PropertyChangingEventHandler PropertyChanging;
public void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
public void NotifyPropertyChanging(string property)
{
if (PropertyChanging != null)
PropertyChanging(this, new PropertyChangingEventArgs(property));
}
}
In the constructor in app.xaml.cs, I have the following:
TaskMasterDataContext = new TaskMasterDataContext();
if (!TaskMasterDataContext.DatabaseExists())
{
TaskMasterDataContext.CreateDatabase();
DatabaseHelper.SetupDatabase(TaskMasterDataContext);
}
and here is the TaskMasterDataContext.cs code
public class TaskMasterDataContext : DataContext
{
public TaskMasterDataContext() : base("Data Source=isostore:/TaskMasterData.sdf")
{
}
public Table<Task> Tasks;
}
public static class DatabaseHelper
{
public static void SetupDatabase(TaskMasterDataContext dataContext)
{
string category = string.Empty;
var tasks = new List<Task>();
for (int i = 0; i < 20; i++)
{
tasks.Add(new Task()
{
Id = System.Guid.NewGuid().ToString(),
Category = GetCategoryString(i),
CreateDate = DateTime.Now,
DueDate = DateTime.Now.AddDays(new Random().Next(1, 30)),
IsComplete = false,
Name = String.Format("{0} Task # {1}", GetCategoryString(i), i)
});
}
dataContext.Tasks.InsertAllOnSubmit(tasks);
dataContext.SubmitChanges();
}
private static string GetCategoryString(int i)
{
if (i%2 == 0)
return "home";
if (i%3 == 0)
return "personal";
return "work";
}
}
The DatabaseHelper class is just there to populate the DB with some test data after its created.
I hope this helps.

Resources