I have some questions regarding factories, repositories and services in DDD. I have the following entities: Folder, file, FileData.
In my opinion the "Folder" is an aggregate root and should have the responsibility of creating the File and FileData object.
So my first question is should I use a factory to create this aggreate or is it up to the repository? At this time I have 2 repositories, one for Folder and another for File, but it seems to me I should merge them together. The following code snippet, shows my Folder Repository, which is located in my infrastructure layer:
public class FolderRepository : IFolderRepository
{
#region Fields
private readonly IFolderContext _context;
private readonly IUnitOfWork _unitOfWork;
#endregion
#region Constructor
public FolderRepository(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
_context = _unitOfWork.Context as IFolderContext;
}
#endregion
public IUnitOfWork UnitOfWork
{
get { return _unitOfWork; }
}
public IQueryable<Folder> All
{
get { return _context.Folders; }
}
public Folder Find(Guid id)
{
return _context.Folders.Find(id);
}
public void InsertGraph(Folder entity)
{
_context.Folders.Add(entity);
}
public void InsertOrUpdate(Folder entity)
{
if (entity.Id == Guid.Empty)
{
_context.SetAdd(entity);
}
else
{
_context.SetModified(entity);
}
}
public bool Delete(Guid id)
{
var folder = this.Find(id) ?? _context.Folders.Find(id);
_context.Folders.Remove(folder);
return folder == null;
}
public int AmountOfFilesIncluded(Folder folder)
{
throw new NotImplementedException();
//return folder.Files.Count();
}
public void Dispose()
{
_context.Dispose();
}
}
Next I have created a service in my application layer, this is called "IoService". I have my doubts about the location of the service. Should it be moved to the domain layer?
public class IoService : IIoService
{
#region Fields
private readonly IFolderRepository _folderRepository;
private readonly IFileRepository _fileRepository;
private readonly IUserReferenceRepository _userReferenceRepository;
#endregion
#region Constructor
public IoService(IFolderRepository folderRepository, IFileRepository fileRepository, IUserReferenceRepository userReferenceRepository)
{
if(folderRepository == null)
throw new NullReferenceException("folderRepository");
if(fileRepository == null)
throw new NullReferenceException("fileRepository");
if (userReferenceRepository == null)
throw new NullReferenceException("userReferenceRepository");
_folderRepository = folderRepository;
_fileRepository = fileRepository;
_userReferenceRepository = userReferenceRepository;
}
#endregion
#region Folder Methods
/// <summary>
/// Create a new 'Folder'
/// </summary>
/// <param name="userReference"></param>
/// <param name="name"></param>
/// <param name="parentFolder"></param>
/// <param name="userIds">The given users represent who have access to the folder</param>
/// <param name="keywords"></param>
/// <param name="share"></param>
public void AddFolder(UserReference userReference, string name, Folder parentFolder = null, IList<Guid> userIds = null, IEnumerable<string> keywords = null, bool share = false)
{
var userReferenceList = new List<UserReference> { userReference };
if (userIds != null && userIds.Any())
{
userReferenceList.AddRange(userIds.Select(id => _userReferenceRepository.Find(id)));
}
var folder = new Folder
{
Name = name,
ParentFolder = parentFolder,
Shared = share,
Deleted = false,
CreatedBy = userReference,
UserReferences = userReferenceList
};
if (keywords != null)
{
folder.Keywords = keywords.Select(keyword =>
new Keyword
{
Folder = folder,
Type = "web",
Value = keyword,
}).ToList();
}
//insert into repository
_folderRepository.InsertOrUpdate(folder);
//save
_folderRepository.UnitOfWork.Save();
}
/// <summary>
/// Get 'Folder' by it's id
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public Folder GetFolder(Guid id)
{
return _folderRepository.Find(id);
}
#endregion
#region File Methods
/// <summary>
/// Add a new 'File'
/// </summary>
/// <param name="userReference"></param>
/// <param name="folder"></param>
/// <param name="data"></param>
/// <param name="name"></param>
/// <param name="title"></param>
/// <param name="keywords"></param>
/// <param name="shared"></param>
public void AddFile(UserReference userReference, Folder folder, FileData data, string name, string title = "", IEnumerable<string> keywords = null, bool shared = false)
{
var file = new File
{
Name = name,
Folder = folder,
FileData = data,
CreatedBy = userReference,
Type = data.Type
};
if (keywords != null)
{
file.Keywords = keywords.Select(keyword =>
new Keyword
{
File = file,
Type = "web",
Value = keyword,
}).ToList();
}
folder.Files.Add(file);
folder.Updated = DateTime.UtcNow;
_folderRepository.InsertOrUpdate(folder);
//save
_folderRepository.UnitOfWork.Save();
}
/// <summary>
/// Get 'File' by it's id
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public File GetFile(Guid id)
{
return _fileRepository.Find(id);
}
#endregion
}
To summarize:
Should I use the service for creating the folder object. Or should the service just use a factory, which have the responsibility of creating the object and send the created object to the repository? What about dependency injection in the service, should I inject my services from the UI layer with IOC containers like Unity or should I just hardcode the dependencies in the service?
Thanks
So my first question is should I use a factory to create this aggregate
or is it up to the repository?
A factory is responsible for creation while a repository is responsible for persistence. Upon reconstitution, the repository will effectively create instances. However, often times this creation process is done with reflection and doesn't go through a factory to prevent initialization that should only occur at creation time.
At this time I have 2 repositories, one for Folder and another for
File, but it seems to me I should merge them together.
In DDD, you'd have a repository for each aggregate. This repository would be responsible for persisting all entities and value objects that are part of the aggregate.
I have my doubts about the location of the service. Should it be moved
to the domain layer?
IMO, an application service can be placed into the domain layer since it already serves as a facade and keeping them together would bring the benefits of cohesion. One thought about the IoService is that methods such as AddFile would usually be parameterized by aggregate identities as opposed to instances. Since the application service already references a repository, it can load the appropriate aggregates as needed. Otherwise, calling code would be responsible for calling the repository.
Should I use the service for creating the folder object. Or should the
service just use a factory, which have the responsibility of creating
the object and send the created object to the repository?
The IoService looks good as is except for the previous comment about being parameterized by identities rather than instances.
What about dependency injection in the service, should I inject my
services from the UI layer with IOC containers like Unity or should I
just hardcode the dependencies in the service?
This is a matter of preference. If you can benefit from using an IoC container then use it. However, don't use it just to use it. You are already doing dependency injection, just without a fancy IoC container.
SAMPLE
class File
{
public File(string name, Folder folder, FileData data, UserReference createdBy, IEnumerable<string> keywords = null)
{
//...
}
}
...
class Folder
{
public File AddFile(string name, FileData data, UserReference createdBy, IEnumerable<string> keywords = null)
{
var file = new File(name, this, data, createdBy, keywords)
this.Files.Add(file);
this.Updated = DateTime.UtcNow;
return file;
}
}
...
public void AddFile(UserReference userReference, Guid folderId, FileData data, string name, string title = "", IEnumerable<string> keywords = null, bool shared = false)
{
var folder = _folderRepository.Find(folderId);
if (folder == null)
throw new Exception();
folder.AddFile(name, data, keywords);
_folderRepository.InsertOrUpdate(folder);
_folderRepository.UnitOfWork.Save();
}
In this example, more of the behavior is delegated to the Folder aggregate and File entity. The application service simple calls the appropriate methods on the aggregate.
Related
Please forgive me if this has already been asked before. I looked around, but my situation didn't come into play on any answered question I came across.
I'm Using SignalR 2.2.0
Setup: I have a WebAPI 2 web application (call it API For short) that holds my hub called ChatHub. I have a MVC site (MVC) that is calling my hub on the API site.
Both of these sites are on the same server just different ports. I am using VS 2013 and when I test locally, my local system also uses the same ports...also the url to the API site is loaded from the web config which is different for Release and Debug and local(so the url is correct and the ports on the server are fine...really on thing wrong is OnDisconnectednot getting fired)
After a lot of trial and error and searching, I finally got a test application up. Everything was working perfect. Then I had to modify my hub fit into the business model. IE Take the in memory list of users and messages and record them in the database (among other things). Everything work perfectly when running locally, however once the sites are published to IIS on the server....The OnDisconnected is never called. On the test app / when running locally, this is almost instantly hit by most browsers. But even after waiting 15+ minutes, the method is still not fired.
Here is my hub:(shorted for clarity)
/// <summary>
/// Chat Hub Class
/// </summary>
public class ChatHubAsync : Hub
{
#region Data Members
IDemographicsProvider DemographicsProvider { get; set;}
IChatRepository Repo { get; set; }
#endregion
#region CTOR
/// <summary>
/// Unity Constructor
/// </summary>
[InjectionConstructor]
public ChatHubAsync()
: this(new ChatRepositoryEF(), DataProviders.Demographics)
{
}
/// <summary>
/// Constructor for Chat Hub
/// </summary>
/// <param name="Repository"></param>
/// <param name="Demographics"></param>
public ChatHubAsync(IChatRepository Repository, IDemographicsProvider Demographics)
{
Repo = Repository;
DemographicsProvider = Demographics;
}
#endregion
#region Methods
/// <summary>
/// On Connected method to call base class
/// </summary>
/// <returns></returns>
public override async Task OnConnected()
{
await base.OnConnected();
}
/// <summary>
/// Connect to Hub
/// </summary>
/// <returns>Void</returns>
public async Task Connect()
{
if (await Repo.GetUser(Context.ConnectionId) == null)
{
await RemoveDuplicates(getGuidFromQueryString("userID"), getGuidFromQueryString("groupID"));
var user = await CreateUser();
await Repo.Connect(user);
await Clients.Caller.onConnected(user);
}
}
/// <summary>
/// Add User To Group
/// </summary>
/// <returns></returns>
public async Task AddToGroup()
{
Guid id = getGroupFromQueryString();
if (id != Guid.Empty)
{
string groupID = id.ToString();
var user = await Repo.GetUser(Context.ConnectionId);
try
{
if(user == null)
{
await Connect();
user = await Repo.GetUser(Context.ConnectionId);
}
await Groups.Add(Context.ConnectionId, groupID);
var users = await Repo.OnlineUsers(id);
var messages = await Repo.RetrieveMessages(id, 20);
var status = await Repo.AddOnlineUserToGroup(Context.ConnectionId, id);
await Clients.Caller.onGroupJoined(user, users, messages, status);
Clients.Group(groupID, Context.ConnectionId).onNewUserConnected(user);
}
catch(Exception E)
{
Console.WriteLine(E.Message);
}
}
}
/// .....More Methods that are irrelevant....
/// <summary>
/// Disconnect from Hub
/// </summary>
/// <param name="stopCalled"></param>
/// <returns></returns>
public override async Task OnDisconnected(bool stopCalled)
{
try
{
var item = await Repo.GetUser(Context.ConnectionId);
if (item != null)
{
if (item.GroupID != null && item.GroupID != Guid.Empty)
{
var id = item.GroupID.ToString();
Repo.Disconnect(Context.ConnectionId);
Clients.OthersInGroup(id).onUserDisconnected(Context.ConnectionId, item.UserName);
Groups.Remove(Context.ConnectionId, id);
}
}
}
catch (Exception E)
{
Console.WriteLine(E.Message);
}
await base.OnDisconnected(stopCalled);
}
#endregion
#region private Messages
private async Task<IOnlineUser> CreateUser()
{
///Code removed
}
private Guid getGroupFromQueryString()
{
return getGuidFromQueryString("groupID");
}
private Guid getGuidFromQueryString(string name)
{
Guid id;
try
{
var item = getItemFromQueryString(name);
if (Guid.TryParse(item, out id))
{
return id;
}
throw new Exception("Not a Valid Guid");
}
catch(Exception E)
{
Console.WriteLine(E.Message);
return Guid.Empty;
}
}
private async Task RemoveDuplicates(Guid User, Guid Group)
{
///Code removed
}
#endregion
}
UPDATE:
I have no idea why, but once I removed the calls to the database (ALL CALLS TO THE DATABASE) and went back to in memory lists, On Disconnected started getting called again.
Added back any call to the database using either straight sql or EntityFramework and the OnDisconnected stopped getting called.
Any ideas why adding database calls would cause the onDisconnected to stop getting called?
In case anyone else is having this issue. The problem was caused by the application not impersonating the correct user....it worked fine in visual studio because when impersonation failed to use the provided user, it used me. Out side of VS it didn't have that option and tried using the machine account which failed to log in. This was fixed by adding the impersonation in the application pool instead of using it in the web config. I was having issues with this earlier in the project when using async calls, but I thought I had those fixed with a few changes to the web config. I worked for all the async calls that I was making, but it was still failing in a few select areas that was causing the OnDisconnect to not fire on the server.
I have created the following singleton class to handle a Redis connection, and expose BookSleeve functionality:
public class RedisConnection
{
private static RedisConnection _instance = null;
private BookSleeve.RedisSubscriberConnection _channel;
private readonly int _db;
private readonly string[] _keys; // represent channel name
public BookSleeve.RedisConnection _connection;
/// <summary>
/// Initialize all class parameters
/// </summary>
private RedisConnection(string serverList, int db, IEnumerable<string> keys)
{
_connection = ConnectionUtils.Connect(serverList);
_db = db;
_keys = keys.ToArray();
_connection.Closed += OnConnectionClosed;
_connection.Error += OnConnectionError;
// Create a subscription channel in redis
_channel = _connection.GetOpenSubscriberChannel();
// Subscribe to the registered connections
_channel.Subscribe(_keys, OnMessage);
// Dirty hack but it seems like subscribe returns before the actual
// subscription is properly setup in some cases
while (_channel.SubscriptionCount == 0)
{
Thread.Sleep(500);
}
}
/// <summary>
/// Do something when a message is received
/// </summary>
/// <param name="key"></param>
/// <param name="data"></param>
private void OnMessage(string key, byte[] data)
{
// since we are just interested in pub/sub, no data persistence is active
// however, if the persistence flag is enabled, here is where we can save the data
// The key is the stream id (channel)
//var message = RedisMessage.Deserialize(data);
var message = Helpers.BytesToString(data);
if (true) ;
//_publishQueue.Enqueue(() => OnReceived(key, (ulong)message.Id, message.Messages));
}
public static RedisConnection GetInstance(string serverList, int db, IEnumerable<string> keys)
{
if (_instance == null)
{
// could include some sort of lock for thread safety
_instance = new RedisConnection(serverList, db, keys);
}
return _instance;
}
private static void OnConnectionClosed(object sender, EventArgs e)
{
// Should we auto reconnect?
if (true)
{
;
}
}
private static void OnConnectionError(object sender, BookSleeve.ErrorEventArgs e)
{
// How do we bubble errors?
if (true)
{
;
}
}
}
In OnMessage(), var message = RedisMessage.Deserialize(data); is commented out due to the following error:
RedisMessage is inaccessible due to its protection level.
RedisMessage is an abstract class in BookSleeve, and I'm a little stuck on why I cannot use this.
I ran into this issue because as I send messages to a channel (pub/sub) I may want to do something with them in OnMessage() - for example, if a persistence flag is set, I may choose to begin recording data. The issue is that the data is serialized at this point, and I wish to deserialize it (to string) and and persist it in Redis.
Here is my test method:
[TestMethod]
public void TestRedisConnection()
{
// setup parameters
string serverList = "dbcache1.local:6379";
int db = 0;
List<string> eventKeys = new List<string>();
eventKeys.Add("Testing.FaucetChannel");
BookSleeve.RedisConnection redisConnection = Faucet.Services.RedisConnection.GetInstance(serverList, db, eventKeys)._connection;
// broadcast to a channel
redisConnection.Publish("Testing.FaucetChannel", "a published value!!!");
}
Since I wasn't able to make use of the Deserialize() method, I created a static helper class:
public static class Helpers
{
/// <summary>
/// Serializes a string to bytes
/// </summary>
/// <param name="val"></param>
/// <returns></returns>
public static byte[] StringToBytes(string str)
{
try
{
byte[] bytes = new byte[str.Length * sizeof(char)];
System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
return bytes;
}
catch (Exception ex)
{
/* handle exception omitted */
return null;
}
}
/// <summary>
/// Deserializes bytes to string
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public static string BytesToString(byte[] bytes)
{
string set;
try
{
char[] chars = new char[bytes.Length / sizeof(char)];
System.Buffer.BlockCopy(bytes, 0, chars, 0, bytes.Length);
return new string(chars);
}
catch (Exception ex)
{
// removed error handling logic!
return null;
}
}
}
Unfortunately, this is not properly deserializing the string back to its original form, and what I'm getting is something like this: 異汢獩敨慶畬㩥ㄠ, rather than the actual original text.
Suggestions?
RedisMessage represents a pending request that is about to be sent to the server; there are a few concrete implementations of this, typically relating to the nature and quantity of the parameters to be sent. It makes no sense to "deserialize" (or even "serialize") a RedisMessage - that is not their purpose. The only thing it is sensible to do is to Write(...) them to a Stream.
If you want information about a RedisMessage, then .ToString() has an overview, but this is not round-trippable and is frankly intended for debugging.
RedisMessage is an internal class; an implementation detail. Unless you're working on a pull request to the core code, you should never need to interact with a RedisMessage.
At a similar level, there is RedisResult which represents a response coming back from the server. If you want a quick way of getting data from that, fortunately that is much simpler:
object val = result.Parse(true);
(the true means "speculatively test to see if the data looks like a string"). But again, this is an internal implementation detail that you should not have to work with.
Obviously this was an encoding type issue, and in the meantime, with a little glance at this link, I simply added the encoding type of UTF8, and the output looks fine:
#region EncodingType enum
/// <summary>
/// Encoding Types.
/// </summary>
public enum EncodingType
{
ASCII,
Unicode,
UTF7,
UTF8
}
#endregion
#region ByteArrayToString
/// <summary>
/// Converts a byte array to a string using Unicode encoding.
/// </summary>
/// <param name="bytes">Array of bytes to be converted.</param>
/// <returns>string</returns>
public static string ByteArrayToString(byte[] bytes)
{
return ByteArrayToString(bytes, EncodingType.Unicode);
}
/// <summary>
/// Converts a byte array to a string using specified encoding.
/// </summary>
/// <param name="bytes">Array of bytes to be converted.</param>
/// <param name="encodingType">EncodingType enum.</param>
/// <returns>string</returns>
public static string ByteArrayToString(byte[] bytes, EncodingType encodingType)
{
System.Text.Encoding encoding=null;
switch (encodingType)
{
case EncodingType.ASCII:
encoding=new System.Text.ASCIIEncoding();
break;
case EncodingType.Unicode:
encoding=new System.Text.UnicodeEncoding();
break;
case EncodingType.UTF7:
encoding=new System.Text.UTF7Encoding();
break;
case EncodingType.UTF8:
encoding=new System.Text.UTF8Encoding();
break;
}
return encoding.GetString(bytes);
}
#endregion
--UPDATE--
Even simpler: var message = Encoding.UTF8.GetString(data);
I am using the Unit of Work and Generic Repository pattern. Here is the statement that checks for a duplicate entry:
int id = int.Parse(beer.id); //id comes from the item we're hoping to insert
if (_unitOfWork.BeerRepository.GetByID(id) == null)
\\create a new model br
_unitOfWork.BeerRepository.Insert(br);
_unitOfWork.save();
Apparently this is failing to check to see if the beer is already in the database because I get this inner exception:
Violation of PRIMARY KEY constraint 'PK_Beers_3214EC2703317E3D'.
Cannot insert duplicate key in object 'dbo.Beers'.\r\nThe statement
has been terminated.
I also get this message:
An error occurred while saving entities that do not expose foreign
key properties for their relationships. The EntityEntries property
will return null because a single entity cannot be identified as the
source of the exception. Handling of exceptions while saving can be
made easier by exposing foreign key properties in your entity types.
See the InnerException for details.
The UnitOfWork class has my BeerRecommenderContext which implements DbContext and the UnitOfWork has a generic repository for each entity:
namespace BeerRecommender.Models
{
public class GenericRepository<TEntity> where TEntity : class
{
internal BeerRecommenderContext context;
internal DbSet<TEntity> dbSet;
public GenericRepository(BeerRecommenderContext context)
{
this.context = context;
this.dbSet = context.Set<TEntity>();
}
public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}
public virtual TEntity GetByID(object id)
{
return dbSet.Find(id);
}
public virtual void Insert(TEntity entity)
{
dbSet.Add(entity);
}
public virtual void Delete(object id)
{
TEntity entityToDelete = dbSet.Find(id);
Delete(entityToDelete);
}
public virtual void Delete(TEntity entityToDelete)
{
if (context.Entry(entityToDelete).State == EntityState.Detached)
{
dbSet.Attach(entityToDelete);
}
dbSet.Remove(entityToDelete);
}
public virtual void Update(TEntity entityToUpdate)
{
dbSet.Attach(entityToUpdate);
context.Entry(entityToUpdate).State = EntityState.Modified;
}
}
}
I have a similar usage of repository using code-first. Occasionally, I would see conflicts like the one you described. My issue was with change tracking across multiple processes. Are you inserting items into the database inside one process (using a single entity context)?
If you are, you should look at the Merge Options available with Entity Framework. If you are using the default merge option (AppendOnly), then you could be querying the in memory context instead of going to the database. This could cause the behaviour you are describing.
Unfortunately, as far as I understand, all the merge options are not yet exposed to Code-First. You can choose the default (AppendOnly) or NoTracking, which will go to the database every time.
Hope this helps,
Davin
Our site uses ADFS for auth. To reduce the cookie payload on every request we're turning IsSessionMode on (see Your fedauth cookies on a diet).
The last thing we need to do to get this working in our load balanced environment is to implement a farm ready SecurityTokenCache. The implementation seems pretty straightforward, I'm mainly interested in finding out if there are any gotchas we should consider when dealing with SecurityTokenCacheKey and the TryGetAllEntries and TryRemoveAllEntries methods (SecurityTokenCacheKey has a custom implementation of the Equals and GetHashCode methods).
Does anyone have an example of this? We're planning on using AppFabric as the backing store but an example using any persistent store would be helpful- database table, Azure table-storage, etc.
Here are some places I've searched:
In Hervey Wilson's PDC09
session he uses a
DatabaseSecurityTokenCache. I haven't been able to find the sample
code for his session.
On page 192 of Vittorio Bertocci's excellent
book, "Programming Windows Identity Foundation" he mentions uploading
a sample implementation of an Azure ready SecurityTokenCache to the
book's website. I haven't been able to find this sample either.
Thanks!
jd
3/16/2012 UPDATE
Vittorio's blog links to a sample using the new .net 4.5 stuff:
ClaimsAwareWebFarm
This sample is an answer to the feedback we got from many of you guys: you wanted a sample showing a farm ready session cache (as opposed to a tokenreplycache) so that you can use sessions by reference instead of exchanging big cookies; and you asked for an easier way of securing cookies in a farm.
To come up with a working implementation we ultimately had to use reflector to analyze the different SessionSecurityToken related classes in Microsoft.IdentityModel. Below is what we came up with. This implementation is deployed on our dev and qa environments, seems to be working fine, it's resiliant to app pool recycles etc.
In global.asax:
protected void Application_Start(object sender, EventArgs e)
{
FederatedAuthentication.ServiceConfigurationCreated += this.OnServiceConfigurationCreated;
}
private void OnServiceConfigurationCreated(object sender, ServiceConfigurationCreatedEventArgs e)
{
var sessionTransforms = new List<CookieTransform>(new CookieTransform[]
{
new DeflateCookieTransform(),
new RsaEncryptionCookieTransform(
e.ServiceConfiguration.ServiceCertificate),
new RsaSignatureCookieTransform(
e.ServiceConfiguration.ServiceCertificate)
});
// following line is pseudo code. use your own durable cache implementation.
var durableCache = new AppFabricCacheWrapper();
var tokenCache = new DurableSecurityTokenCache(durableCache, 5000);
var sessionHandler = new SessionSecurityTokenHandler(sessionTransforms.AsReadOnly(),
tokenCache,
TimeSpan.FromDays(1));
e.ServiceConfiguration.SecurityTokenHandlers.AddOrReplace(sessionHandler);
}
private void WSFederationAuthenticationModule_SecurityTokenValidated(object sender, SecurityTokenValidatedEventArgs e)
{
FederatedAuthentication.SessionAuthenticationModule.IsSessionMode = true;
}
DurableSecurityTokenCache.cs:
/// <summary>
/// Two level durable security token cache (level 1: in memory MRU, level 2: out of process cache).
/// </summary>
public class DurableSecurityTokenCache : SecurityTokenCache
{
private ICache<string, byte[]> durableCache;
private readonly MruCache<SecurityTokenCacheKey, SecurityToken> mruCache;
/// <summary>
/// The constructor.
/// </summary>
/// <param name="durableCache">The durable second level cache (should be out of process ie sql server, azure table, app fabric, etc).</param>
/// <param name="mruCapacity">Capacity of the internal first level cache (in-memory MRU cache).</param>
public DurableSecurityTokenCache(ICache<string, byte[]> durableCache, int mruCapacity)
{
this.durableCache = durableCache;
this.mruCache = new MruCache<SecurityTokenCacheKey, SecurityToken>(mruCapacity, mruCapacity / 4);
}
public override bool TryAddEntry(object key, SecurityToken value)
{
var cacheKey = (SecurityTokenCacheKey)key;
// add the entry to the mru cache.
this.mruCache.Add(cacheKey, value);
// add the entry to the durable cache.
var keyString = GetKeyString(cacheKey);
var buffer = this.GetSerializer().Serialize((SessionSecurityToken)value);
this.durableCache.Add(keyString, buffer);
return true;
}
public override bool TryGetEntry(object key, out SecurityToken value)
{
var cacheKey = (SecurityTokenCacheKey)key;
// attempt to retrieve the entry from the mru cache.
value = this.mruCache.Get(cacheKey);
if (value != null)
return true;
// entry wasn't in the mru cache, retrieve it from the app fabric cache.
var keyString = GetKeyString(cacheKey);
var buffer = this.durableCache.Get(keyString);
var result = buffer != null;
if (result)
{
// we had a cache miss in the mru cache but found the item in the durable cache...
// deserialize the value retrieved from the durable cache.
value = this.GetSerializer().Deserialize(buffer);
// push this item into the mru cache.
this.mruCache.Add(cacheKey, value);
}
return result;
}
public override bool TryRemoveEntry(object key)
{
var cacheKey = (SecurityTokenCacheKey)key;
// remove the entry from the mru cache.
this.mruCache.Remove(cacheKey);
// remove the entry from the durable cache.
var keyString = GetKeyString(cacheKey);
this.durableCache.Remove(keyString);
return true;
}
public override bool TryReplaceEntry(object key, SecurityToken newValue)
{
var cacheKey = (SecurityTokenCacheKey)key;
// remove the entry in the mru cache.
this.mruCache.Remove(cacheKey);
// remove the entry in the durable cache.
var keyString = GetKeyString(cacheKey);
// add the new value.
return this.TryAddEntry(key, newValue);
}
public override bool TryGetAllEntries(object key, out IList<SecurityToken> tokens)
{
// not implemented... haven't been able to find how/when this method is used.
tokens = new List<SecurityToken>();
return true;
//throw new NotImplementedException();
}
public override bool TryRemoveAllEntries(object key)
{
// not implemented... haven't been able to find how/when this method is used.
return true;
//throw new NotImplementedException();
}
public override void ClearEntries()
{
// not implemented... haven't been able to find how/when this method is used.
//throw new NotImplementedException();
}
/// <summary>
/// Gets the string representation of the specified SecurityTokenCacheKey.
/// </summary>
private string GetKeyString(SecurityTokenCacheKey key)
{
return string.Format("{0}; {1}; {2}", key.ContextId, key.KeyGeneration, key.EndpointId);
}
/// <summary>
/// Gets a new instance of the token serializer.
/// </summary>
private SessionSecurityTokenCookieSerializer GetSerializer()
{
return new SessionSecurityTokenCookieSerializer(); // may need to do something about handling bootstrap tokens.
}
}
MruCache.cs:
/// <summary>
/// Most recently used (MRU) cache.
/// </summary>
/// <typeparam name="TKey">The key type.</typeparam>
/// <typeparam name="TValue">The value type.</typeparam>
public class MruCache<TKey, TValue> : ICache<TKey, TValue>
{
private Dictionary<TKey, TValue> mruCache;
private LinkedList<TKey> mruList;
private object syncRoot;
private int capacity;
private int sizeAfterPurge;
/// <summary>
/// The constructor.
/// </summary>
/// <param name="capacity">The capacity.</param>
/// <param name="sizeAfterPurge">Size to make the cache after purging because it's reached capacity.</param>
public MruCache(int capacity, int sizeAfterPurge)
{
this.mruList = new LinkedList<TKey>();
this.mruCache = new Dictionary<TKey, TValue>(capacity);
this.capacity = capacity;
this.sizeAfterPurge = sizeAfterPurge;
this.syncRoot = new object();
}
/// <summary>
/// Adds an item if it doesn't already exist.
/// </summary>
public void Add(TKey key, TValue value)
{
lock (this.syncRoot)
{
if (mruCache.ContainsKey(key))
return;
if (mruCache.Count + 1 >= this.capacity)
{
while (mruCache.Count > this.sizeAfterPurge)
{
var lru = mruList.Last.Value;
mruCache.Remove(lru);
mruList.RemoveLast();
}
}
mruCache.Add(key, value);
mruList.AddFirst(key);
}
}
/// <summary>
/// Removes an item if it exists.
/// </summary>
public void Remove(TKey key)
{
lock (this.syncRoot)
{
if (!mruCache.ContainsKey(key))
return;
mruCache.Remove(key);
mruList.Remove(key);
}
}
/// <summary>
/// Gets an item. If a matching item doesn't exist null is returned.
/// </summary>
public TValue Get(TKey key)
{
lock (this.syncRoot)
{
if (!mruCache.ContainsKey(key))
return default(TValue);
mruList.Remove(key);
mruList.AddFirst(key);
return mruCache[key];
}
}
/// <summary>
/// Gets whether a key is contained in the cache.
/// </summary>
public bool ContainsKey(TKey key)
{
lock (this.syncRoot)
return mruCache.ContainsKey(key);
}
}
ICache.cs:
/// <summary>
/// A cache.
/// </summary>
/// <typeparam name="TKey">The key type.</typeparam>
/// <typeparam name="TValue">The value type.</typeparam>
public interface ICache<TKey, TValue>
{
void Add(TKey key, TValue value);
void Remove(TKey key);
TValue Get(TKey key);
}
Here is a sample that I wrote. I use Windows Azure to store the tokens forever, defeating any possible replay.
http://tokenreplaycache.codeplex.com/releases/view/76652
You will need to place this in your web.config:
<service>
<securityTokenHandlers>
<securityTokenHandlerConfiguration saveBootstrapTokens="true">
<tokenReplayDetection enabled="true" expirationPeriod="50" purgeInterval="1">
<replayCache type="LC.Security.AzureTokenReplayCache.ACSTokenReplayCache,LC.Security.AzureTokenReplayCache, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</tokenReplayDetection>
</securityTokenHandlerConfiguration>
</securityTokenHandlers>
</service>
I have a consistent, repeatable 120 second hang whenever the application calls
this.cacheProvider.Add(new CacheItem(cacheKey, data, this.regionName), cachePolicy);
at line 60 of the CachedDataSource.cs of the sample.. The .Add method is internal to Microsoft's DLL and I don't have code to it. Here are my parameters:
cacheKey = "listofCompanies"
data = // this is an EF 4.0 database first model class with 70 entries... result from IQueryable
this.regionName = "companies"
Reproducing the error:
I have a database-first EF4.0 project that I recently upgraded to 4.1 by adding the "EntityFramework" reference and a ContextGenerator to my DAL.
If I undo these changes, then my application is instantly performant.
My DAL and repository are stored in a separate DLL from my MVC application. Not sure if this is playing a part of the issue.
About my repository
/// Sample repository. Note that I return List<T> as IEnumerable,
/// and I use IDisposable
///
public class CompanyRepository : DisposableBase, ICompanyRepository
{
public IEnumerable<CompanyDetail> GetOneCompany(int? CompanyID)
{
var t = from c in _entities.CompanyDetail
where c.CompanyID == CompanyID.Value
select c;
return t.ToList();
}
}
/// <summary>
/// Disposable implementation based on advice from this link:
/// from Http://www.asp.net/entity-framework/tutorials/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application
/// </summary>
public class DisposableBase : IDisposable
{
protected TLSAdminEntities1 _entities;
public DisposableBase()
{
_entities = new TLSAdminEntities1();
disposed = false;
}
private bool disposed ;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
_entities.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
Question
Is this a bug, or am I using EF4.1, or the Caching layer incorrectly?
You mention that data is the result of IQueryable. Have you tried to perform .ToList() first on the data before sending it over to cache?