Can Client of a WCF Duplex Service(TCP binding) send and recive at the same time? - wcf

My code at the moment looks like this:
Server side:
#region IClientCallback interface
interface IClientCallback
{
[OperationContract(IsOneWay = true)]
void ReceiveWcfElement(WcfElement wcfElement);
}
#endregion
#region IService interface
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IClientCallback))]
interface IService
{
[OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = false)]
void ReadyToReceive(string userName, int source, string ostatniTypWiadomosci);
[OperationContract(IsOneWay = false, IsInitiating = false, IsTerminating = false)]
bool SendWcfElement(WcfElement wcfElement);
[OperationContract(IsOneWay = false, IsInitiating = true, IsTerminating = false)]
List<int> Login(Client name, string password, bool isAuto, bool isSuperMode);
}
#endregion
#region Public enums/event args
public delegate void WcfElementsReceivedFromClientEventHandler(object sender, WcfElementsReceivedFromClientEventArgs e);
public class WcfElementsReceivedFromClientEventArgs : EventArgs
{
public string UserName;
}
public class ServiceEventArgs : EventArgs
{
public WcfElement WcfElement;
public Client Person;
}
#endregion
#region Service
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class Service : IService
{
#region Instance fields
//thread sync lock object
private static readonly Object SyncObj = new Object();
//callback interface for clients
IClientCallback _callback;
//delegate used for BroadcastEvent
public delegate void ChatEventHandler(object sender, ServiceEventArgs e);
public static event ChatEventHandler ChatEvent;
private ChatEventHandler _myEventHandler;
//holds a list of clients, and a delegate to allow the BroadcastEvent to work
//out which chatter delegate to invoke
static readonly Dictionary<Client, ChatEventHandler> Clients = new Dictionary<Client, ChatEventHandler>();
//current person
private Client _client;
#endregion
#region Helpers
private bool CheckIfPersonExists(string name)
{
return Clients.Keys.Any(p => p.UserName.Equals(name, StringComparison.OrdinalIgnoreCase));
}
private ChatEventHandler getPersonHandler(string name)
{
foreach (var c in Clients.Keys.Where(c => c.UserName.Equals(name, StringComparison.OrdinalIgnoreCase)))
{
ChatEventHandler chatTo;
Clients.TryGetValue(c, out chatTo);
return chatTo;
}
return null;
}
private Client GetPerson(string name)
{
return Clients.Keys.FirstOrDefault(c => c.UserName.Equals(name, StringComparison.OrdinalIgnoreCase));
}
#endregion
#region IService implementation
public List<int> Login(Client client, string password, bool isAuto, bool isSuperMode)
{
if (client.ElementsVersions == null)
{
client.ElementsVersions = new WcfElement(WcfElement.RodzajWiadomosci.VersionControl, client.UserName);
}
//create a new ChatEventHandler delegate, pointing to the MyEventHandler() method
_myEventHandler = MyEventHandler;
lock (SyncObj)
{
if (!CheckIfPersonExists(client.UserName))
{
_client = client;
Clients.Add(client, _myEventHandler);
}
else
{
_client = client;
foreach (var c in Clients.Keys.Where(c => c.UserName.Equals(client.UserName)))
{
ChatEvent -= Clients[c];
Clients.Remove(c);
break;
}
Clients[client] = _myEventHandler;
}
_client.LockObj = new object();
}
_callback = OperationContext.Current.GetCallbackChannel<IClientCallback>();
ChatEvent += _myEventHandler;
var rValue = isAuto ? bazaDanych.Login(client.UserName, isSuperMode) : bazaDanych.Login(client.UserName, password);
return rValue;
}
public void PerformDataSync(Client c)
{
WcfElement wcfDelete = null;
WcfElement wcfUpdate = null;
//...
//this method prepares elements for client
//when done it adds them to clients queue (List<WcfElement)
try
{
var counter = 0;
if (wcfDelete != null)
{
foreach (var wcf in WcfElement.SplitWcfElement(wcfDelete, false))//split message into small ones
{
c.AddElementToQueue(wcf, counter++);
}
}
if (wcfUpdate != null)
{
foreach (var wcf in WcfElement.SplitWcfElement(wcfUpdate, true))
{
c.AddElementToQueue(wcf, counter++);
}
}
SendMessageToGui(string.Format("Wstępna synchronizacja użytkownika {0} zakończona.", c.UserName));
c.IsSynchronized = true;
}
catch (Exception e)
{
}
}
private void SendMessageToClient(object sender, EventArgs e)
{
var c = (Client) sender;
if (c.IsReceiving || c.IsSending)
{
return;
}
c.IsReceiving = true;
var wcfElement = c.GetFirstElementFromQueue();
if (wcfElement == null)
{
c.IsReceiving = false;
return;
}
Clients[c].Invoke(this, new ServiceEventArgs { Person = c, WcfElement = wcfElement });
}
public void ReadyToReceive(string userName)
{
var c = GetPerson(userName);
c.IsSending = false;
c.IsReceiving = false;
if (c.IsSynchronized)
{
SendMessageToClient(c, null);
}
else
{
PerformDataSync(c);
}
}
public bool SendWcfElement(WcfElement wcfElement)
{
var cl = GetPerson(wcfElement.UserName);
cl.IsSending = true;
if (wcfElement.WcfElementVersion != bazaDanych.WcfElementVersion) return false;
//method processes messages and if needed creates creates WcfElements which are added to every clients queue
return ifSuccess;
}
#endregion
#region private methods
private void MyEventHandler(object sender, ServiceEventArgs e)
{
try
{
_callback.ReceiveWcfElement(e.WcfElement);
}
catch (Exception ex)
{
}
}
#endregion
}
#endregion
Client side in a moment
#region Client class
[DataContract]
public class Client
{
#region Instance Fields
/// <summary>
/// The UserName
/// </summary>
[DataMember]
public string UserName { get; set; }
[DataMember]
public WcfElement ElementsVersions { get; set; }
private bool _isSynchronized;
public bool IsSynchronized
{
get { return _isSynchronized; }
set
{
_isSynchronized = value;
}
}
public bool IsSending { get; set; }
public bool IsReceiving { get; set; }
private List<WcfElement> ElementsQueue { get; set; }
public object LockObj { get; set; }
public void AddElementToQueue(WcfElement wcfElement, int position = -1)
{
try
{
lock (LockObj)
{
if (ElementsQueue == null) ElementsQueue = new List<WcfElement>();
if (position != -1 && position <= ElementsQueue.Count)
{
try
{
ElementsQueue.Insert(position, wcfElement);
}
catch (Exception e)
{
}
}
else
{
try
{
//dodaje na koncu
ElementsQueue.Add(wcfElement);
}
catch (Exception e)
{
}
}
}
}
catch (Exception e)
{
}
}
public WcfElement GetFirstElementFromQueue()
{
if (ElementsQueue == null) return null;
if (ElementsQueue.Count > 0)
{
var tmp = ElementsQueue[0];
ElementsQueue.RemoveAt(0);
return tmp;
}
return null;
}
#endregion
#region Ctors
/// <summary>
/// Assign constructor
/// </summary>
/// <param name="userName">The userName to use for this client</param>
public Client(string userName)
{
UserName = userName;
}
#endregion
}
#endregion
ProxySingletion:
[CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant, UseSynchronizationContext = false)]
public sealed class ProxySingleton : IClientCallback
{
#region Instance Fields
private static ProxySingleton _singleton;
public static bool IsConnected;
private static readonly object SingletonLock = new object();
private ServiceProxy _proxy;
private Client _myPerson;
private delegate void HandleDelegate(Client[] list);
private delegate void HandleErrorDelegate();
//main proxy event
public delegate void ProxyEventHandler(object sender, ProxyEventArgs e);
public static event ProxyEventHandler ProxyEvent;
//callback proxy event
public delegate void ProxyCallBackEventHandler(object sender, ProxyCallBackEventArgs e);
public static event ProxyCallBackEventHandler ProxyCallBackEvent;
#endregion
#region Ctor
/// <summary>
/// Blank constructor
/// </summary>
private ProxySingleton()
{
}
#endregion
#region Public Methods
#region IClientCallback implementation
public void ReceiveWcfElement(WcfElement wcfElement)
{
//process received data
//...
ReadyToReceive();
}
#endregion
public void ReadyToReceive()
{
try
{
if (bazaDanych.Dane.Client.IsSending) return;
var w = bazaDanych.Dane.Client.GetFirstElementFromQueue();
if (w != null)
{
SendWcfElement(w);
return;
}
_proxy.ReadyToReceive(bazaDanych.Dane.Client.UserName, source, ostatniTypWiadomosci);
}
catch (Exception)
{
IsConnected = false;
}
}
public static WcfElement CurrentWcfElement;
public bool SendWcfElement(WcfElement wcfElement)
{
if (bazaDanych.Dane.Client.IsReceiving)
{
bazaDanych.Dane.Client.AddElementToQueue(wcfElement);
return true;
}
bazaDanych.Dane.Client.IsSending = true;
foreach (var wcfElementSplited in WcfElement.SplitWcfElement(wcfElement, true))
{
CurrentWcfElement = wcfElementSplited;
try
{
var r = _proxy.SendWcfElement(wcfElementSplited);
CurrentWcfElement = null;
}
catch (Exception e)
{
IsConnected = false;
return false;
}
}
bazaDanych.Dane.Client.IsSending = false;
ReadyToReceive();
return true;
}
public void ListenForConnectOrReconnect(EventArgs e)
{
SendWcfElement(WcfElement.GetVersionElement());//send wcfelement for perform PerformDataSync
ReadyToReceive();
}
public static bool IsReconnecting;
public bool ConnectOrReconnect(bool shouldRaiseEvent = true)
{
if (IsReconnecting)
{
return IsConnected;
}
if (IsConnected) return true;
IsReconnecting = true;
bazaDanych.Dane.Client.IsReceiving = false;
bazaDanych.Dane.Client.IsSending = false;
bazaDanych.Dane.Client.IsSynchronized = false;
try
{
var site = new InstanceContext(this);
_proxy = new ServiceProxy(site);
var list = _proxy.Login(bazaDanych.Dane.Client, bazaDanych.Dane.UserPassword, bazaDanych.Dane.UserIsAuto, bazaDanych.Dane.UserIsSuperMode);
bazaDanych.Dane.UserRights.Clear();
bazaDanych.Dane.UserRights.AddRange(list);
IsConnected = true;
if (shouldRaiseEvent) ConnectOrReconnectEvent(null);
}
catch (Exception e)
{
IsConnected = false;
}
IsReconnecting = false;
return IsConnected;
}
}
#endregion
At the moment my app works like this:
After successful login every client sends WcfElements(which contains bunch of list with ids and versions of elements). Then it sends ReadyToReceive one way message which after login fires performsync method. That method prepares data for client and sends first of them using one way receive method. IF there is more than one wcfelement to send then only last one is marked as last. Client responds with ReadyToReceive after every successful receive from Server. All up to this point works quite well. Problem starts later. Mostly packages are lost (method receiveWcfElement). Server has marked that client is receiving and maybe processing message and is waitng for readytoreceive packet, which will never be send because of lost element.
I've made it like this because as far as I know client can't send and receive at the same time. I've tried this and got this problem:
If client send wcfElement with SendWcfElement method and server due to processing this element created another element which was supposed to be ssend back to client then client whoud have faulted proxy if callback was send before sendWcfElement returned true indicating that method was completed.
Now I wonder if it is possible for client to send and receive at the same time using two way methods ?

I ended up with to services(two connections). One for connection from client to server and another with callback which handles connection from server to client.

Related

Read ASP.NET Core logs per scope/operation

Let's say I have several ASP.NET BackgroundServices and each is logging to its own scope/operation (OP1 and OP2).
public class MyBackgroundService1 : BackgroundService
{
private readonly ILogger<MyBackgroundService1> _logger;
public MyBackgroundService1(ILogger<MyBackgroundService1> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var activity = new Activity("OP1");
activity.Start();
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Hello from MyBackgroundService1");
await Task.Delay(5000, stoppingToken);
}
}
}
public class MyBackgroundService2 : BackgroundService
{
private readonly ILogger<MyBackgroundService2> _logger;
public MyBackgroundService2(ILogger<MyBackgroundService2> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var activity = new Activity("OP2");
activity.Start();
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Hello from MyBackgroundService2");
await Task.Delay(1000, stoppingToken);
}
}
}
Now I would like to use Blazor and want to display a table per operation with all corresponding logs.
Example output
OP1 Logs:
Hello from MyBackgroundService1
Hello from MyBackgroundService1
OP2 Logs:
Hello from MyBackgroundService2
Hello from MyBackgroundService2
How would I do that?
For this purpose, you need to create a log provider that stores the information in the database and then retrieves the information from the log table.
First, create a class to store logs in the database as follows:
public class DBLog
{
public int DBLogId { get; set; }
public string? LogLevel { get; set; }
public string? EventName { get; set; }
public string? Message { get; set; }
public string? StackTrace { get; set; }
public DateTime CreatedDate { get; set; }=DateTime.Now;
}
Now, We need to create a custom DBLogger. The DBLogger class inherits from the ILogger interface and has three methods, the most important of which is the Log method, which is actually called every time the Logger is called in the program. To read more about the other two methods, you can refer here.
public class DBLogger:ILogger
{
private readonly LogLevel _minLevel;
private readonly DbLoggerProvider _loggerProvider;
private readonly string _categoryName;
public DBLogger(
DbLoggerProvider loggerProvider,
string categoryName
)
{
_loggerProvider= loggerProvider ?? throw new ArgumentNullException(nameof(loggerProvider));
_categoryName= categoryName;
}
public IDisposable BeginScope<TState>(TState state)
{
return new NoopDisposable();
}
public bool IsEnabled(LogLevel logLevel)
{
return logLevel >= _minLevel;
}
public void Log<TState>(
LogLevel logLevel,
EventId eventId,
TState state,
Exception exception,
Func<TState, Exception, string> formatter)
{
if (!IsEnabled(logLevel))
{
return;
}
if (formatter == null)
{
throw new ArgumentNullException(nameof(formatter));
}
var message = formatter(state, exception);
if (exception != null)
{
message = $"{message}{Environment.NewLine}{exception}";
}
if (string.IsNullOrEmpty(message))
{
return;
}
var dblLogItem = new DBLog()
{
EventName = eventId.Name,
LogLevel = logLevel.ToString(),
Message = $"{_categoryName}{Environment.NewLine}{message}",
StackTrace=exception?.StackTrace
};
_loggerProvider.AddLogItem(dblLogItem);
}
private class NoopDisposable : IDisposable
{
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
}
}
}
Now we need to create a custom log provider so that an instance of the above custom database logger (DBLogger) can be created.
public class DbLoggerProvider : ILoggerProvider
{
private readonly CancellationTokenSource _cancellationTokenSource = new();
private readonly IList<DBLog> _currentBatch = new List<DBLog>();
private readonly TimeSpan _interval = TimeSpan.FromSeconds(2);
private readonly BlockingCollection<DBLog> _messageQueue = new(new ConcurrentQueue<DBLog>());
private readonly Task _outputTask;
private readonly IServiceProvider _serviceProvider;
private bool _isDisposed;
public DbLoggerProvider(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
_outputTask = Task.Run(ProcessLogQueue);
}
public ILogger CreateLogger(string categoryName)
{
return new DBLogger(this, categoryName);
}
private async Task ProcessLogQueue()
{
while (!_cancellationTokenSource.IsCancellationRequested)
{
while (_messageQueue.TryTake(out var message))
{
try
{
_currentBatch.Add(message);
}
catch
{
//cancellation token canceled or CompleteAdding called
}
}
await SaveLogItemsAsync(_currentBatch, _cancellationTokenSource.Token);
_currentBatch.Clear();
await Task.Delay(_interval, _cancellationTokenSource.Token);
}
}
internal void AddLogItem(DBLog appLogItem)
{
if (!_messageQueue.IsAddingCompleted)
{
_messageQueue.Add(appLogItem, _cancellationTokenSource.Token);
}
}
private async Task SaveLogItemsAsync(IList<DBLog> items, CancellationToken cancellationToken)
{
try
{
if (!items.Any())
{
return;
}
// We need a separate context for the logger to call its SaveChanges several times,
// without using the current request's context and changing its internal state.
var scopeFactory = _serviceProvider.GetRequiredService<IServiceScopeFactory>();
using (var scope = scopeFactory.CreateScope())
{
var scopedProvider = scope.ServiceProvider;
using (var newDbContext = scopedProvider.GetRequiredService<ApplicationDbContext>())
{
foreach (var item in items)
{
var addedEntry = newDbContext.DbLogs.Add(item);
}
await newDbContext.SaveChangesAsync(cancellationToken);
// ...
}
}
}
catch
{
// don't throw exceptions from logger
}
}
[SuppressMessage("Microsoft.Usage", "CA1031:catch a more specific allowed exception type, or rethrow the exception",
Justification = "don't throw exceptions from logger")]
private void Stop()
{
_cancellationTokenSource.Cancel();
_messageQueue.CompleteAdding();
try
{
_outputTask.Wait(_interval);
}
catch
{
// don't throw exceptions from logger
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_isDisposed)
{
try
{
if (disposing)
{
Stop();
_messageQueue.Dispose();
_cancellationTokenSource.Dispose();
}
}
finally
{
_isDisposed = true;
}
}
}
}
In the end, it is enough to call this custom log provider (DbLoggerProvider) in the Startup.cs or Program.cs class.
var serviceProvider = app.ApplicationServices.CreateScope().ServiceProvider;
loggerFactory.AddProvider(new DbLoggerProvider(serviceProvider));
From now on, every time we call the _logger.LogInformation("");, the log information will also be stored in the database.
Note: Because the number of calls to record logs in the database may be high, a concurrent queue is used to store logs.
If you like, you can refer to my repository that implements the same method.
In order to log the areas separately(scope/operation), you can create several different DBLoggers to store the information in different tables.

Custom model binder with inheritance using Web API and RavenDB

I'm developing a simple web app where I need to bind all types implementing and interface of a specific type. My interface has one single property like this
public interface IContent {
string Id { get;set; }
}
a common class using this interface would look like this
public class Article : IContent {
public string Id { get;set; }
public string Heading { get;set; }
}
to be clean here the article class is just one of many different classes implementing IContent so therefor I need a generic way of storing and updating these types.
So in my controller I have the put method like this
public void Put(string id, [System.Web.Http.ModelBinding.ModelBinder(typeof(ContentModelBinder))] IContent value)
{
// Store the updated object in ravendb
}
and the ContentBinder
public class ContentModelBinder : System.Web.Http.ModelBinding.IModelBinder {
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) {
actionContext.ControllerContext.Request.Content.ReadAsAsync<Article>().ContinueWith(task =>
{
Article model = task.Result;
bindingContext.Model = model;
});
return true;
}
}
The code above does not work because it does not seem to get hold of the Heading property even though if I use the default model binder it binds the Heading correctly.
So, in the BindModel method I guess I need to load the correct object from ravendb based on the Id and then update the complex object using some kind of default model binder or so? This is where I need some help.
Marcus, following is an example which would work fine for both Json and Xml formatter.
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Runtime.Serialization;
using System.Web.Http;
using System.Web.Http.SelfHost;
namespace Service
{
class Service
{
private static HttpSelfHostServer server = null;
private static string baseAddress = string.Format("http://{0}:9095/", Environment.MachineName);
static void Main(string[] args)
{
HttpSelfHostConfiguration config = new HttpSelfHostConfiguration(baseAddress);
config.Routes.MapHttpRoute("Default", "api/{controller}/{id}", new { id = RouteParameter.Optional });
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Objects;
try
{
server = new HttpSelfHostServer(config);
server.OpenAsync().Wait();
Console.WriteLine("Service listenting at: {0} ...", baseAddress);
TestWithHttpClient("application/xml");
TestWithHttpClient("application/json");
Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine("Exception Details:\n{0}", ex.ToString());
}
finally
{
if (server != null)
{
server.CloseAsync().Wait();
}
}
}
private static void TestWithHttpClient(string mediaType)
{
HttpClient client = new HttpClient();
MediaTypeFormatter formatter = null;
// NOTE: following any settings on the following formatters should match
// to the settings that the service's formatters have.
if (mediaType == "application/xml")
{
formatter = new XmlMediaTypeFormatter();
}
else if (mediaType == "application/json")
{
JsonMediaTypeFormatter jsonFormatter = new JsonMediaTypeFormatter();
jsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Objects;
formatter = jsonFormatter;
}
HttpRequestMessage request = new HttpRequestMessage();
request.RequestUri = new Uri(baseAddress + "api/students");
request.Method = HttpMethod.Get;
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaType));
HttpResponseMessage response = client.SendAsync(request).Result;
Student std = response.Content.ReadAsAsync<Student>().Result;
Console.WriteLine("GET data in '{0}' format", mediaType);
if (StudentsController.CONSTANT_STUDENT.Equals(std))
{
Console.WriteLine("both are equal");
}
client = new HttpClient();
request = new HttpRequestMessage();
request.RequestUri = new Uri(baseAddress + "api/students");
request.Method = HttpMethod.Post;
request.Content = new ObjectContent<Person>(StudentsController.CONSTANT_STUDENT, formatter);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaType));
Student std1 = client.SendAsync(request).Result.Content.ReadAsAsync<Student>().Result;
Console.WriteLine("POST and receive data in '{0}' format", mediaType);
if (StudentsController.CONSTANT_STUDENT.Equals(std1))
{
Console.WriteLine("both are equal");
}
}
}
public class StudentsController : ApiController
{
public static readonly Student CONSTANT_STUDENT = new Student() { Id = 1, Name = "John", EnrolledCourses = new List<string>() { "maths", "physics" } };
public Person Get()
{
return CONSTANT_STUDENT;
}
// NOTE: specifying FromBody here is not required. By default complextypes are bound
// by formatters which read the body
public Person Post([FromBody] Person person)
{
if (!ModelState.IsValid)
{
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, this.ModelState));
}
return person;
}
}
[DataContract]
[KnownType(typeof(Student))]
public abstract class Person : IEquatable<Person>
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
// this is ignored
public DateTime DateOfBirth { get; set; }
public bool Equals(Person other)
{
if (other == null)
return false;
if (ReferenceEquals(this, other))
return true;
if (this.Id != other.Id)
return false;
if (this.Name != other.Name)
return false;
return true;
}
}
[DataContract]
public class Student : Person, IEquatable<Student>
{
[DataMember]
public List<string> EnrolledCourses { get; set; }
public bool Equals(Student other)
{
if (!base.Equals(other))
{
return false;
}
if (this.EnrolledCourses == null && other.EnrolledCourses == null)
{
return true;
}
if ((this.EnrolledCourses == null && other.EnrolledCourses != null) ||
(this.EnrolledCourses != null && other.EnrolledCourses == null))
return false;
if (this.EnrolledCourses.Count != other.EnrolledCourses.Count)
return false;
for (int i = 0; i < this.EnrolledCourses.Count; i++)
{
if (this.EnrolledCourses[i] != other.EnrolledCourses[i])
return false;
}
return true;
}
}
}
I used #kiran-challa solution and added TypeNameHandling on Json media type formatter's SerializerSettings.

using RavenDB with ServiceStack

I read this post by Phillip Haydon about how to use NHibernate/RavenDB with ServiceStack.
I don't see the point about getting the IDocumentStore and open new session every time i need something from the db like this:
public class FooService : ServiceBase<Foo>
{
public IDocumentStore RavenStore{ get; set; }
protected override object Run(ProductFind request)
{
using (var session = RavenStore.OpenSession())
{
// Do Something...
return new FooResponse{/*Object init*/};
}
}
}
Why cant i just use one session per request and when the request is ended, commit the changes or roll them back according to the response status?
If my approach is fine, than how can i implement it?
here is my attempt:
I created this class:
public class RavenSession : IRavenSession
{
#region Data Members
private readonly IDocumentStore _store;
private IDocumentSession _innerSession;
#endregion
#region Properties
public IDocumentSession InnerSession
{
get { return _innerSession ?? (_innerSession = _store.OpenSession()); }
}
#endregion
#region Ctor
public RavenSession(IDocumentStore store)
{
_store = store;
}
#endregion
#region Public Methods
public void Commit()
{
if (_innerSession != null)
{
try
{
InnerSession.SaveChanges();
}
finally
{
InnerSession.Dispose();
}
}
}
public void Rollback()
{
if (_innerSession != null)
{
InnerSession.Dispose();
}
}
#endregion
#region IDocumentSession Delegation
public ISyncAdvancedSessionOperation Advanced
{
get { return InnerSession.Advanced; }
}
public void Delete<T>(T entity)
{
InnerSession.Delete(entity);
}
public ILoaderWithInclude<object> Include(string path)
{
return InnerSession.Include(path);
}
public ILoaderWithInclude<T> Include<T, TInclude>(Expression<Func<T, object>> path)
{
return InnerSession.Include<T, TInclude>(path);
}
public ILoaderWithInclude<T> Include<T>(Expression<Func<T, object>> path)
{
return InnerSession.Include(path);
}
public T Load<T>(string id)
{
return InnerSession.Load<T>(id);
}
public T[] Load<T>(params string[] ids)
{
return InnerSession.Load<T>(ids);
}
public T Load<T>(ValueType id)
{
return InnerSession.Load<T>(id);
}
public T[] Load<T>(IEnumerable<string> ids)
{
return InnerSession.Load<T>(ids);
}
public IRavenQueryable<T> Query<T, TIndexCreator>() where TIndexCreator : AbstractIndexCreationTask, new()
{
return InnerSession.Query<T, TIndexCreator>();
}
public IRavenQueryable<T> Query<T>()
{
return InnerSession.Query<T>();
}
public IRavenQueryable<T> Query<T>(string indexName)
{
return InnerSession.Query<T>(indexName);
}
public void Store(dynamic entity, string id)
{
InnerSession.Store(entity, id);
}
public void Store(object entity, Guid etag, string id)
{
InnerSession.Store(entity, etag, id);
}
public void Store(object entity, Guid etag)
{
InnerSession.Store(entity, etag);
}
public void Store(dynamic entity)
{
InnerSession.Store(entity);
}
#endregion
}
And now my service looks like this:
public class FooService : ServiceBase<Foo>
{
public IRavenSession RavenSession { get; set; }
protected override object Run(ProductFind request)
{
// Do Something with RavenSession...
return new FooResponse {/*Object init*/};
}
}
but i still need to find a way to know when the request is ended for commit/rollback the changes.
the best way i found is by using ResponseFilters:
public class AppHost : AppHostBase
{
public AppHost()
: base("", typeof (Foo).Assembly, typeof (FooService).Assembly)
{
}
public override void Configure(Container container)
{
// Some Configuration...
this.ResponseFilters.Add((httpReq, httpResp, respnseDto) =>
{
var currentSession = (RavenSession) this.Container.Resolve<IRavenSession>();
if (!httpResp.IsErrorResponse())
{
currentSession.Commit();
}
else
{
currentSession.Rollback();
}
});
// Some Configuration...
}
}
I am sure that there is a better way to do this but how?
I just included this on the Configure method for the AppHost
var store = new DocumentStore()
{
Url = "http://127.0.0.1:8080",
DefaultDatabase = "Test"
}.Initialize();
container.Register(store);
container.Register(c => c.Resolve<IDocumentStore>().OpenSession()).ReusedWithin(ReuseScope.Request);
You can put it aside on module and initialize it.
Then in your services just add a constructor that accepts IDocumentSession
public HelloService : Service {
private readonly IDocumentSession session;
public HelloService(IDocumentSession session) {
this.session = session;
}
}
And you're good to go.
Filtering the response in ServiceStack
The ways to introspect the Response in ServiceStack is with either:
The Response Filter or Response Filter Attributes or other custom hooks
Overriding AppHost.ServiceExceptionHandler or custom OnAfterExecute() hook
Some other notes that might be helpful:
ServiceStack's built-in IOC (Funq) now supports RequestScope
You can add IDisposable to a base class which gets called immediately after the service has finished executing, e.g. if you were to use an RDBMS:
public class FooServiceBase : IService, IDisposable
{
public IDbConnectionFactory DbFactory { get; set; }
private IDbConnection db;
public IDbConnection Db
{
get { return db ?? (db = DbFactory.OpenDbConnection()); }
}
public object Any(ProductFind request)
{
return new FooResponse {
Result = Db.Id<Product>(request.Id)
};
}
public void Dispose()
{
if (db != null) db.Dispose();
}
}
I tried the answer given by Felipe Leusin but it has not worked for me. The main thing that I want to achieve is having a single DocumentSession.SaveChanges call per request. After looking at the RacoonBlog DocumentSession lifecycle management and at ServiceStack request lifecycle events I put together a configuration that works for me:
public override void Configure(Funq.Container container)
{
RequestFilters.Add((httpReq, httpRes, requestDto) =>
{
IDocumentSession documentSession = Container.Resolve<IDocumentStore>().OpenSession();
Container.Register<IDocumentSession>(documentSession);
});
ResponseFilters.Add((httpReq, httpRes, requestDto) =>
{
using (var documentSession = Container.Resolve<IDocumentSession>())
{
if (documentSession == null)
return;
if (httpRes.StatusCode >= 400 && httpRes.StatusCode < 600)
return;
documentSession.SaveChanges();
}
});
var documentStore = new DocumentStore
{
ConnectionStringName = "RavenDBServer",
DefaultDatabase = "MyDatabase",
}.Initialize();
container.Register(documentStore);
I am using funq with RequestScope for my RavenSession, and now i update it to:
public class RavenSession : IRavenSession, IDisposable
{
#region Data Members
private readonly IDocumentStore _store;
private readonly IRequestContext _context;
private IDocumentSession _innerSession;
#endregion
#region Properties
public IDocumentSession InnerSession
{
get { return _innerSession ?? (_innerSession = _store.OpenSession()); }
}
#endregion
#region Ctor
public RavenSession(IDocumentStore store, IRequestContext context)
{
_store = store;
_context = context;
}
#endregion
#region IDocumentSession Delegation
public ISyncAdvancedSessionOperation Advanced
{
get { return InnerSession.Advanced; }
}
public void Delete<T>(T entity)
{
InnerSession.Delete(entity);
}
public ILoaderWithInclude<object> Include(string path)
{
return InnerSession.Include(path);
}
public ILoaderWithInclude<T> Include<T, TInclude>(Expression<Func<T, object>> path)
{
return InnerSession.Include<T, TInclude>(path);
}
public ILoaderWithInclude<T> Include<T>(Expression<Func<T, object>> path)
{
return InnerSession.Include(path);
}
public T Load<T>(string id)
{
return InnerSession.Load<T>(id);
}
public T[] Load<T>(params string[] ids)
{
return InnerSession.Load<T>(ids);
}
public T Load<T>(ValueType id)
{
return InnerSession.Load<T>(id);
}
public T[] Load<T>(IEnumerable<string> ids)
{
return InnerSession.Load<T>(ids);
}
public IRavenQueryable<T> Query<T, TIndexCreator>() where TIndexCreator : AbstractIndexCreationTask, new()
{
return InnerSession.Query<T, TIndexCreator>();
}
public IRavenQueryable<T> Query<T>()
{
return InnerSession.Query<T>();
}
public IRavenQueryable<T> Query<T>(string indexName)
{
return InnerSession.Query<T>(indexName);
}
public void Store(dynamic entity, string id)
{
InnerSession.Store(entity, id);
}
public void Store(object entity, Guid etag, string id)
{
InnerSession.Store(entity, etag, id);
}
public void Store(object entity, Guid etag)
{
InnerSession.Store(entity, etag);
}
public void Store(dynamic entity)
{
InnerSession.Store(entity);
}
#endregion
#region Implementation of IDisposable
public void Dispose()
{
if (_innerSession != null)
{
var httpResponse = _context.Get<IHttpResponse>();
try
{
if (!httpResponse.IsErrorResponse())
{
_innerSession.SaveChanges();
}
}
finally
{
_innerSession.Dispose();
}
}
}
#endregion
}
but this would not work because:
1) although i am using RequestScope, no one is register the IRequestContext of the request so funq cant resolve my RavenSession.
2) funq does not run the Dispose method after the request is done, which is odd.

multithreading with fluent nhibernate

private ISessionFactory GetSessionFactory(string sessionFactoryConfigPath)
{
GetFullSessionFactoryFor(sessionFactoryConfigPath);
while (!sessionFactoryReady) Thread.Sleep(1000);
return (ISessionFactory)sessionFactories[sessionFactoryConfigPath];
}
private void GetFullSessionFactory(string sessionFactoryConfigPath)
{
ThreadPool.QueueUserWorkItem(state =>
{
ISessionFactory sessionFactory=null;
FluentConfiguration fluentConfiguration = fluentConfiguration.ExposeConfiguration(c => c.SetProperty("sessionfactoryname","somevalue"))
.Mappings(m =>
{
m.FluentMappings
.AddFromAssembly(Assembly.Load("nameofassembly"))
.Conventions.Add(DefaultLazy.Always(),
OptimisticLock.Is(x => x.All()),
DynamicUpdate.AlwaysTrue(),
DynamicInsert.AlwaysFalse(),
DefaultCascade.None()
)
.Conventions.AddFromAssemblyOf<"SomeConvention">();
}
);
sessionFactory = fluentConfiguration.BuildSessionFactory();
});
}
I am creating minisession factory on main thread(not shown here) and full session factory on second thread.
The problem is when it hits buildsessionfactory the code never returns back.Am i doing it right?
public class NHibernateBaseDAO<T>
{
public NHibernateBaseDAO(string sessionFactoryConfigPath, int sessionId)
{
SessionFactoryConfigPath = sessionFactoryConfigPath;
SessionId = sessionId;
public bool Save(T entity)
{
bool saveSuccessful = true;
try
{
NHibernateSession.Save(entity);
}
catch (NHibernate.HibernateException)
{
saveSuccessful = false;
}
return saveSuccessful;
}
public bool SaveOrUpdate(T entity)
{
bool saveSuccessful = true;
try
{
NHibernateSession.SaveOrUpdate(entity);
}
catch (NHibernate.HibernateException)
{
saveSuccessful = false;
}
return saveSuccessful;
}
public void Delete(T entity)
{
NHibernateSession.Delete(entity);
}
public void CommitChanges()
{
if (NHibernateSessionManager.Instance.HasOpenTransactionOn(SessionFactoryConfigPath, this.SessionId))
{
NHibernateSessionManager.Instance.GetSessionFrom(SessionFactoryConfigPath, this.SessionId).Flush();
NHibernateSessionManager.Instance.CommitTransactionOn(SessionFactoryConfigPath, this.SessionId);
}
else
{
NHibernateSessionManager.Instance.GetSessionFrom(SessionFactoryConfigPath, this.SessionId).Flush();
}
}
public void BeginTransaction()
{
NHibernateSessionManager.Instance.BeginTransactionOn(SessionFactoryConfigPath, this.SessionId);
}
public void RollbackTransaction()
{
NHibernateSessionManager.Instance.RollbackTransactionOn(SessionFactoryConfigPath, this.SessionId);
}
public bool IsDirty()
{
return NHibernateSession.IsDirty();
}
public IQueryable<T> Query() {
return (IQueryable<T>)NHibernateSession.Query<T>();
}
protected ISession NHibernateSession
{
get
{
return NHibernateSessionManager.Instance.GetSessionFrom(SessionFactoryConfigPath, this.SessionId);
}
}
protected readonly string SessionFactoryConfigPath;
protected int SessionId;
protected System.Data.IDbConnection DbConnection
{
get { return NHibernateSessionManager.Instance.GetDbConnection(SessionFactoryConfigPath, this.SessionId); }
}
/// <summary>
/// Return a list of object arrays. use this for general queries
/// </summary>
public System.Collections.IEnumerable GetSqlQuery(string queryString, IList<Criterion> criterion, Type returnType)
{
queryString += CriteriaToSql(criterion);
return NHibernateSession.CreateQuery(queryString).Enumerable();
}
protected ICriteria AddCriteria(IList<Criterion> criterion)
{
ICriteria criteria = NHibernateSession.CreateCriteria(persistentType);
foreach (Criterion criterium in criterion)
{
switch (criterium.Comparison)
{
case SqlComparison.StartsWith:
criteria.Add(Restrictions.InsensitiveLike(criterium.Property, criterium.Value1.ToString(), MatchMode.Start));
break;
case SqlComparison.Contains:
criteria.Add(Restrictions.InsensitiveLike(criterium.Property, criterium.Value1.ToString(), MatchMode.Anywhere));
break;
case SqlComparison.Equals:
criteria.Add(Restrictions.Eq(criterium.Property, criterium.Value1));
break;
case SqlComparison.Between:
criteria.Add(Restrictions.Between(criterium.Property, criterium.Value1, criterium.Value2));
break;
case SqlComparison.MoreThan:
criteria.Add(Restrictions.Gt(criterium.Property, criterium.Value1));
break;
case SqlComparison.LessThan:
criteria.Add(Restrictions.Lt(criterium.Property, criterium.Value2));
break;
case SqlComparison.InList:
criteria.Add(Restrictions.In(criterium.Property, (System.Collections.IList)criterium.Value1));
break;
}
}
return criteria;
}
protected string CriteriaToSql(IList<Criterion> criterion)
{
}
/// <summary>
/// Get delimiter for data, defaults to ' unless specifed for data type
/// </summary>
protected string[] GetDelimiter(object value)
{
}
public class Criterion
{
public Criterion(string property, SqlComparison comparison, object value1)
{
Property = property;
Comparison = comparison;
Value1 = value1;
}
public Criterion(string property, SqlComparison comparison, object value1, object value2)
{
Property = property;
Comparison = comparison;
Value1 = value1;
Value2 = value2;
}
public Criterion(string property, SqlComparison comparison, object value1, bool not)
{
Property = property;
Comparison = comparison;
Value1 = value1;
Not = not;
}
public Criterion(string property, SqlComparison comparison, object value1, object value2, bool not)
{
Property = property;
Comparison = comparison;
Value1 = value1;
Value2 = value2;
Not = not;
}
public string Property { get; set; }
public bool Not { get; set; }
public SqlComparison Comparison { get; set; }
public object Value1 { get; set; }
public object Value2 { get; set; }
}
public enum SqlComparison { StartsWith, Contains, Equals, Between, MoreThan, LessThan, InList }
}
one last question please. I am using generic class to access sessionfactory so i cannot explicitly access the minisession. with this how do i access minisession based only on certain entities if the full session is not available and full session factory when it is available.
you never set sessionfactoryready to true
Update: a more complete example.
void Main()
{
Database.InitRealFactoryAsync("<sessionFactoryConfigPath>");
var minifactory = Database.GetMiniFactory("<sessionFactoryConfigPath>");
// Do some stuff with minifactory
var realsessionfactory = Database.SessionFactory;
// Do stuff with real factory
}
static class Database
{
private static ISessionFactory sessionFactory;
public void InitRealFactoryAsync(string sessionFactoryConfigPath)
{
ThreadPool.QueueUserWorkItem(state =>
{
sessionFactory = Fluently.Configure()
.Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.Load("nameofassembly"))
.Conventions.Add(DefaultLazy.Always(),
OptimisticLock.Is(x => x.All()),
DynamicUpdate.AlwaysTrue(),
DynamicInsert.AlwaysFalse(),
DefaultCascade.None())
.Conventions.AddFromAssemblyOf<FoxproDateConvention>())
.BuildSessionFactory();
});
}
public ISessionFactory GetMiniFactory(string sessionFactoryConfigPath)
{
var assembly = Assembly.Load("nameofassembly");
return Fluently.Configure()
.Mappings(m => m.FluentMappings.Add(assembly.GetTypes().Where(Filter).ToArray())
.Conventions.Add(DefaultLazy.Always(),
OptimisticLock.Is(x => x.All()),
DynamicUpdate.AlwaysTrue(),
DynamicInsert.AlwaysFalse(),
DefaultCascade.None())
.Conventions.AddFromAssemblyOf<FoxproDateConvention>())
.BuildSessionFactory();
}
public static ISessionFactory SessionFactory
{
get {
while (sessionFactory == null) Thread.Sleep(1000);
return sessionFactory;
}
}
}
UpdateUpdate:
void Main()
{
Database.InitRealFactoryAsync("<sessionFactoryConfigPath>");
Database.InitMiniFactory("<sessionFactoryConfigPath>");
using (var session = Database.GetSession(true))
{
// Do some stuff where minifactory is enough
}
using (var session = Database.GetSession())
{
// Do stuff with real factory
}
...
}
// class Database
public ISession GetSession()
{
return GetSession(false);
}
public ISession GetSession(bool miniFactoryIsEnough)
{
if (realSessionfactory != null)
return realSessionfactory.OpenSession();
if (miniFactoryIsEnough)
return miniSessionfactory.OpenSession();
else
{
while (realSessionFactory == null) Thread.Sleep(1000);
return realSessionfactory.OpenSession();
}
}
Update: "access minisession based only on certain entities"
you need to specify the type you want to use in the session:
public ISession GetSession(Type persistentType)
{
if (fullSessionfactory != null)
return realSessionfactory.OpenSession();
if (miniFactory.GetClassMetadata(persistentType) != null)
return miniSessionfactory.OpenSession();
else
{
// minifactory doesnt contain the type needed, wait for full factory
while (fullSessionFactory == null) Thread.Sleep(1000);
return fullSessionfactory.OpenSession();
}
}
some additional advice
do not catch (NHibernate.HibernateException)
you lose valuable information and the calling code can't really decide what to do when false is returned
session state is inconsistent see https://stackoverflow.com/a/1819150/671619
FlushMode should be Flushmode.Commit and public void CommitChanges() can be written as
var session = NHibernateSession;
if (session.Transaction.IsActiv)
{
session.Transaction.Commit();
}
cut out the whole sessionId stuff as it seems to provide no value. hold the session instead of sessionId instead

Ninject Intercept any method with certain attribute?

How can I get Ninject.Extensions.Interception to basically let me bind a specific interceptor to any method that has an attribute... psudocode:
Kernel.Intercept(context => context.Binding.HasAttribute<TransactionAttribute>())
.With<TransactionInterceptor>
With a class like:
public SomeClass
{
[TransactionAttribute]
public void SomeTransactedMethod()
{ /*do stuff */ }
}
Assuming that you are using Ninject.Extensions.Interception this should do the trick
public class TransactionInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
// Do something...
}
}
public class TransactionAttribute : InterceptAttribute
{
public override IInterceptor CreateInterceptor(IProxyRequest request)
{
return new TransactionInterceptor();
}
}
public class SomeClass
{
[Transaction]
public virtual void SomeTransactedMethod() { }
}
Make sure that the method that should be intercepted is marked as virtual.
When SomeTransactedMethod() is called it should be intercepted.
var kernel = new StandardKernel();
kernel.Bind<SomeClass>().ToSelf();
var someClass = kernel.Get<SomeClass>();
someClass.SomeTransactedMethod();
UPDATE
You could create a custom planning strategy.
public class CustomPlanningStrategy<TAttribute, TInterceptor> :
NinjectComponent, IPlanningStrategy
where TAttribute : Attribute
where TInterceptor : IInterceptor
{
private readonly IAdviceFactory adviceFactory;
private readonly IAdviceRegistry adviceRegistry;
public CustomPlanningStrategy(
IAdviceFactory adviceFactory, IAdviceRegistry adviceRegistry)
{
this.adviceFactory = adviceFactory;
this.adviceRegistry = adviceRegistry;
}
public void Execute(IPlan plan)
{
var methods = GetCandidateMethods(plan.Type);
foreach (var method in methods)
{
var attributes = method.GetCustomAttributes(
typeof(TAttribute), true) as TAttribute[];
if (attributes.Length == 0)
{
continue;
}
var advice = adviceFactory.Create(method);
advice.Callback = request => request.Kernel.Get<TInterceptor>();
adviceRegistry.Register(advice);
if (!plan.Has<ProxyDirective>())
{
plan.Add(new ProxyDirective());
}
}
}
}
private static IEnumerable<MethodInfo> GetCandidateMethods(Type type)
{
var methods = type.GetMethods(
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance
);
return methods.Where(ShouldIntercept);
}
private static bool ShouldIntercept(MethodInfo methodInfo)
{
return methodInfo.DeclaringType != typeof(object) &&
!methodInfo.IsPrivate &&
!methodInfo.IsFinal;
}
}
This should now work.
var kernel = new StandardKernel();
kernel.Components.Add<IPlanningStrategy,
CustomPlanningStrategy<TransactionAttribute, TransactionInterceptor>>();
kernel.Bind<SomeClass>().ToSelf();
var someClass = kernel.Get<SomeClass>();
someClass.SomeTransactedMethod();
Here is the code that I used for the same purpose
//Code in Bind Module
this.Bind(typeof(ServiceBase<,>))
.ToSelf()
.InRequestScope()
.Intercept()
.With<TransactionInterceptor>();
And
public class TransactionInterceptor : IInterceptor
{
#region Constants and Fields
public ISession session;
private ISessionFactory sessionFactory;
#endregion
#region Constructors and Destructors
public TransactionInterceptor(ISession session, ISessionFactory sessionFactory)
{
this.sessionFactory = sessionFactory;
this.session = session;
}
#endregion
public void Intercept(IInvocation invocation)
{
try
{
if (!session.IsConnected)
session = sessionFactory.OpenSession();
session.BeginTransaction();
invocation.Proceed();
if (this.session == null)
{
return;
}
if (!this.session.Transaction.IsActive)
{
return;
}
else
{
this.session.Transaction.Commit();
}
}
catch (Exception)
{
if (this.session == null)
{
return;
}
if (!this.session.Transaction.IsActive)
{
return;
}
this.session.Transaction.Rollback();
throw;
}
}
}
And code for TransactionAttribute
public class TransactionAttribute : InterceptAttribute
{
#region Public Methods
public override IInterceptor CreateInterceptor(IProxyRequest request)
{
return request.Context.Kernel.Get<TransactionInterceptor>() ;
}
#endregion
}