Creating an nhibernate session per web request with Castle.Facility.AutoTx and Castle.Facility.NHibernate - fluent-nhibernate

I am using Castle Windors and it's AutoTx and the NHibernate Facility by haf. Ultimately I want the benefits of ease of use of the Transaction attribute provided by AutoTx. (ASP.NET MVC 4 project).
I am using Castle.Facilities.NHibernate.ISessionManager to manage my PerWebRequest sessions. I have setup the Windsor Installer as such:
public void Install(Castle.Windsor.IWindsorContainer container, Castle.MicroKernel.SubSystems.Configuration.IConfigurationStore store)
{
container.AddFacility<AutoTxFacility>();
container.Register(Component.For<INHibernateInstaller>().ImplementedBy<NHibernateInstaller>().LifeStyle.Singleton);
container.AddFacility<NHibernateFacility>(f => f.DefaultLifeStyle = DefaultSessionLifeStyleOption.SessionPerWebRequest);
container.Install(FromAssembly.Containing<PersonRepository>());
}
I'm using the DefaultLifeStyle of SessionPerWebRequest, which I expect would do exactly that, provide me with a session that lasts the entire web request, such that all calls to OpenSession on SessionManager within same request use the same session. I'm testing that with the following code:
public class HomeController : Controller
{
private readonly ISessionManager _sessionManager;
public HomeController(ISessionManager sessionManager)
{
_sessionManager = sessionManager;
}
public ActionResult Index()
{
using (var session1 = _sessionManager.OpenSession())
{
var person = session1.Get<Person>(1);
using (var session2 = _sessionManager.OpenSession())
{
var person2 = session2.Get<Person>(1);
}
}
return View();
}
}
and checking the log to see the id of each created session. The id is always different. eg
05/01/2013 11:27:39.109 DEBUG 9 NHibernate.Impl.SessionImpl - [session-id=c1ba248a-14ba-4468-a20c-d6114b7dac61] opened session at timestamp: 634929820591, for session factory: [/ea869bb12b4d4e51b9f431a4f9c9d9fa]
05/01/2013 11:30:36.383 DEBUG 9 NHibernate.Impl.SessionImpl - [session-id=72481180-625d-4085-98e9-929e3fd93e8a] opened session at timestamp: 634929822363, for session factory: [/ea869bb12b4d4e51b9f431a4f9c9d9fa]
It's worth noting that I haven't added anything to the web.config in the way of Handlers. Do I need to? ( I didn't see any documentation suggesting this in the NHib Facility Wiki) Are my expectations that the same Session will always be returned incorrect.
I've had a look through the source code for the facility and do not understand how a session per web request is being instantiated and how multiple calls to OpenSession would result in the same session in the same web request.
The following is how SessionManager is registered with Windsor:
Component.For<ISessionManager>().Instance(new SessionManager(() =>
{
var factory = Kernel.Resolve<ISessionFactory>(x.Instance.SessionFactoryKey);
var s = x.Instance.Interceptor.Do(y => factory.OpenSession(y)).OrDefault(factory.OpenSession());
s.FlushMode = flushMode;
return s;
}))
.Named(x.Instance.SessionFactoryKey + SessionManagerSuffix)
.LifeStyle.Singleton
ISession is registered with Windsor using the following
private IRegistration RegisterSession(Data x, uint index)
{
Contract.Requires(index < 3,
"there are only three supported lifestyles; per transaction, per web request and transient");
Contract.Requires(x != null);
Contract.Ensures(Contract.Result<IRegistration>() != null);
return GetLifeStyle(
Component.For<ISession>()
.UsingFactoryMethod((k, c) =>
{
var factory = k.Resolve<ISessionFactory>(x.Instance.SessionFactoryKey);
var s = x.Instance.Interceptor.Do(y => factory.OpenSession(y)).OrDefault(factory.OpenSession());
s.FlushMode = flushMode;
logger.DebugFormat("resolved session component named '{0}'", c.Handler.ComponentModel.Name);
return s;
}), index, x.Instance.SessionFactoryKey);
}
private ComponentRegistration<T> GetLifeStyle<T>(ComponentRegistration<T> registration, uint index, string baseName)
where T : class
{
Contract.Requires(index < 3,
"there are only three supported lifestyles; per transaction, per web request and transient");
Contract.Ensures(Contract.Result<ComponentRegistration<T>>() != null);
switch (defaultLifeStyle)
{
case DefaultSessionLifeStyleOption.SessionPerTransaction:
if (index == 0)
return registration.Named(baseName + SessionPerTxSuffix).LifeStyle.PerTopTransaction();
if (index == 1)
return registration.Named(baseName + SessionPWRSuffix).LifeStyle.PerWebRequest;
if (index == 2)
return registration.Named(baseName + SessionTransientSuffix).LifeStyle.Transient;
goto default;
case DefaultSessionLifeStyleOption.SessionPerWebRequest:
if (index == 0)
return registration.Named(baseName + SessionPWRSuffix).LifeStyle.PerWebRequest;
if (index == 1)
return registration.Named(baseName + SessionPerTxSuffix).LifeStyle.PerTopTransaction();
if (index == 2)
return registration.Named(baseName + SessionTransientSuffix).LifeStyle.Transient;
goto default;
case DefaultSessionLifeStyleOption.SessionTransient:
if (index == 0)
return registration.Named(baseName + SessionTransientSuffix).LifeStyle.Transient;
if (index == 1)
return registration.Named(baseName + SessionPerTxSuffix).LifeStyle.PerTopTransaction();
if (index == 2)
return registration.Named(baseName + SessionPWRSuffix).LifeStyle.PerWebRequest;
goto default;
default:
throw new FacilityException("invalid index passed to GetLifeStyle<T> - please file a bug report");
}
}
which does register an ISession as PerWebRequest, but I can't see anywhere in the code where that named registration is extracted when a session is required?
Any help on what I need to do get Session per web request working is appreciated.
UPDATE I decided to just replace the code function being passed into SessionManager constructor with code that grabs the ISession from the container, rather than uses the factory. Works perfectly for what I want it to do, including being wrapped in transactions and only opening one session per web request, or transient etc.
Component.For<ISessionManager>().Instance(new SessionManager(() =>
{
var s = Kernel.Resolve<ISession>();
s.FlushMode = flushMode;
return s;
}))
//Component.For<ISessionManager>().Instance(new SessionManager(() =>
//{
// var factory = Kernel.Resolve<ISessionFactory>(x.Instance.SessionFactoryKey);
// var s = x.Instance.Interceptor.Do(y => factory.OpenSession(y)).OrDefault(factory.OpenSession());
// s.FlushMode = flushMode;
// return s;
//}))
.Named(x.Instance.SessionFactoryKey + SessionManagerSuffix)
.LifeStyle.Singleton
Kernel.Resolve() I expect will grab the first registered service in the container. This will be whatever I set the Lifestyle to.

Related

ASP.NET Core + Entity Framework changed data

I'm trying to save only changed entities.
If I remove this if:
if (!period.IsSame(_context.Periods.First(p => p.ID == period.ID)))
everything is fine.
But if I keep it, on the statement _context.Attach(period); or same if I use Update, I get an error:
InvalidOperationException: The instance of entity type 'Period' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked.
I don't know how test that it's really modified.
public async Task<IActionResult> OnPostAsync(List<Period> periods)
{
if (!ModelState.IsValid)
{
return Page();
}
// _context.Periods.Add(Period);
int i = 0;
foreach (var period in periods)
{
TimeOnly startTime = TimeOnly.Parse(Request.Form["StartTime" + i].ToString());
TimeOnly endTime = TimeOnly.Parse(Request.Form["EndTime" + i].ToString());
period.StartHour = startTime.Hour;
period.StartMinute = startTime.Minute;
period.EndHour = endTime.Hour;
period.EndMinute = endTime.Minute;
period.StatusDate = DateTime.Now;
// if it already exists
if (period.ID > 0)
{
// if modified
if (!period.IsSame(_context.Periods.First(p => p.ID == period.ID)))
{
_context.Attach(period);
if (period.Delete)
{
period.Status = (int)Status.deleted;
}
else
{
period.Status = (int)Status.modified;
}
}
}
// if new
else
{
period.Status = (int)Status.created;
_context.Attach(period);
}
i++;
}
await _context.SaveChangesAsync();
return RedirectToPage("./Periods");
}
I have tried both update and attach. I have search for entity tracking but it seems to be detached as soon as it's on a webpage
EF uses concept of change tracking to determine what should be done with entities. By default querying data will lead to context starting to track it hence the exception. You can mitigate it by disabling tracking by default, for example using .AsNoTracking():
if (!period.IsSame(_context.Periods.AsNoTracking().First(p => p.ID == period.ID)))
{
// ...
}
But this is not very advisable approach, due to multiple reasons - possibly change tracker will not detect any changes and you will need to handle that manually, and bigger reason - you will be querying database in a loop which is bad for application performance. Just fetch everything from the database and update it accordingly:
var existingPeriods = await _context.Periods
.Where(p => periods.Select(p => p.ID).Contains(p.ID))
.ToListAsync(); // or ToDictionaryAsync if there a lot of the periods
foreach (var period in periods)
{
var existing = existingPeriods.FirstOrDefault(p => p.ID == period.ID);
if (existing != null)
{
// maybe throw if period.ID != 0
// update data in existing
existing. ... = ...;
}
else
{
// if new ...
_context.Periods.Add(period);
}
await _context.SaveChangesAsync();
}

Xunit Unit Testing for Dot Net core application

I recently started learning Unit Testing and now have the requirement write unit tests using Xunit and Moq for dot net core application.
I can write some very basic but when it comes to write them for complex classes , I am kind of stuck.
Below is the class I will be writing tests for.
public class AgeCategoryRequestHandler : IInventoryRequestHandler<InventoryRequest, HandlerResult>
{
private readonly IRepositoryResolver _repositoryResolver;
Hotels.HBSI.Logging.ILogger logger;
public AgeCategoryRequestHandler(IRepositoryResolver repositoryResolver, Hotels.HBSI.Logging.ILogger iLogger)
{
_repositoryResolver = repositoryResolver;
logger = iLogger;
}
public async Task<HandlerResult> Run(InventoryRequest inventoryRequest)
{
var result = await ProcessRequest(inventoryRequest);
return CreateResponse(inventoryRequest, result);
}
private async Task<int> ProcessRequest(InventoryRequest inventoryRequest)
{
logger.Info("AgeCategory requesthandler processrequest start");
var repository = _repositoryResolver.ResolveEstabAgeCategory();
if (repository is not null)
{
return await repository.InsertUpdateEstabAgeCategoryDetail(inventoryRequest.EstabAgeCategories)
.ConfigureAwait(false);
}
logger.Info("AgeCategory requesthandler processrequest complete");
return InernalError.reponotfound;
}
public HandlerResult CreateResponse(InventoryRequest inventoryRequest, int resultCount)
{
var requestCount = inventoryRequest.EstabAgeCategories.Count;
var handlerResult = new HandlerResult() { Id = RequestHandlerEnum.AgeCategrory.ToInt() };
if (requestCount > 0 && resultCount < requestCount)
{
handlerResult.IsSuccess = false;
handlerResult.ErrorCode = OTAErrorType.InvalidAgeCategory.ToInt();
}
else if (requestCount > 0 || requestCount == resultCount)
{
handlerResult.IsSuccess = true;
handlerResult.ErrorCode = 0;
}
return handlerResult;
}
}
Just to start , IRepositoryResolver and ILogger are in the constructor so I have created mock for these but unable to go beyond that as I am still in initial phase of learning.
Could someone explain me the steps/approach to accomplish this?.
Edit : What I have done so far is below ( can't figure out what are the things to be done and where to start or write )
Edit 2 : Did some more modifications to my test code , can someone comment if I am in right direction ? what else can I test ?
public class AgeCategoryRequestHandlerTest
{
private AgeCategoryRequestHandler _ageCategoryRequestHandler;
private readonly Mock<AgeCategoryRequestHandler> _ageCategory = new Mock<AgeCategoryRequestHandler>();
private readonly Mock<Hotels.HBSI.Logging.ILogger> _mockLogger = new Mock<Hotels.HBSI.Logging.ILogger>();
private readonly Mock<IRepositoryResolver> _mockRepositoryResolver = new Mock<IRepositoryResolver>();
public AgeCategoryRequestHandlerTest()
{
_ageCategoryRequestHandler = new AgeCategoryRequestHandler(_mockRepositoryResolver.Object, _mockLogger.Object);
}
[Fact]
public async void Testtt()
{
var fixture = new Fixture();
var inventory = fixture.Create<InventoryRequest>();
var hndlr = fixture.Create<HandlerResult>();
hndlr.ErrorCode = 0;
int resultCount = 3;
await _ageCategoryRequestHandler.Run(inventory);
HandlerResult response = _ageCategoryRequestHandler.CreateResponse(inventory, resultCount);
Assert.Equal(hndlr.ErrorCode, response.ErrorCode);
}
Tried running Chris B suggested code , was getting type conversion error EstabAgeCategories = new List<int>
Now I have used fixture for creating automatic objects and did some assert values. Below is the code sample
var fixture = new Fixture();
var inventoryRequest = fixture.Create<InventoryRequest>();
_mockRepository
.Setup(x => x.InsertUpdateEstabAgeCategoryDetail(inventoryRequest.EstabAgeCategories))
.ReturnsAsync(6);
_mockRepositoryResolver
.Setup(x => x.ResolveEstabAgeCategory())
.Returns(_mockRepository.Object);
// act
var result = await _ageCategoryRequestHandler.Run(inventoryRequest);
// assert
_mockRepository
.Verify(x => x.InsertUpdateEstabAgeCategoryDetail(inventoryRequest.EstabAgeCategories), Times.Once);
Assert.True(result.Id == 6);
Assert.True(result.ErrorCode == 0);
Assert.True(result.IsSuccess);
From the unit test code you've posted, it looks like you are getting confused on what to test.
Look at your class and identify your "public" interface i.e. what methods can be called from other parts of your code. You should really only test public methods. Private methods are usually tested via public methods.
Looking at AgeCategoryRequestHandler, you have two public methods - Run and CreateResponse. I would question whether CreateResponse needs to be public but we'll leave it for now. For each of these methods, you want to be asserting that the returned value is what you expect given the input value.
private AgeCategoryRequestHandler _ageCategoryRequestHandler;
// Not needed
private readonly Mock<AgeCategoryRequestHandler> _ageCategory = new Mock<AgeCategoryRequestHandler>();
private readonly Mock<Hotels.HBSI.Logging.ILogger> _mockLogger = new Mock<Hotels.HBSI.Logging.ILogger>();
private readonly Mock<IRepositoryResolver> _mockRepositoryResolver = new Mock<IRepositoryResolver>();
public AgeCategoryRequestHandlerTest()
{
_ageCategoryRequestHandler = new AgeCategoryRequestHandler(_mockRepositoryResolver.Object, _mockLogger.Object);
}
The set up of the unit test is going the right way - you have created mocks for your dependencies but I see you have created a mock for the class you are trying to test - this is not needed and can be removed. You want to be testing the actual class itself which you are initializing in the constructor.
public async Task<HandlerResult> Run(InventoryRequest inventoryRequest)
{
var result = await ProcessRequest(inventoryRequest);
return CreateResponse(inventoryRequest, result);
}
private async Task<int> ProcessRequest(InventoryRequest inventoryRequest)
{
_logger.LogInformation("AgeCategory requesthandler processrequest start");
var repository = _repositoryResolver.ResolveEstabAgeCategory();
if (repository != null)
{
return await repository.InsertUpdateEstabAgeCategoryDetail(inventoryRequest.EstabAgeCategories).ConfigureAwait(false);
}
_logger.LogInformation("AgeCategory requesthandler processrequest complete");
return 0;
}
We can test the public Run method by looking at the method and seeing what it is going to do when executed. Firstly, it's going to call a private method ProcessRequest. Inside ProcessRequest, the IRepositoryResolver dependency is going to be used. This means we need to "set up" this dependency in our unit test to satisfy the if (repository != null) condition.
I assume the IRepositoryResolver returns another interface (?) - something like:
public interface IRepository
{
Task<int> InsertUpdateEstabAgeCategoryDetail(List<int> x);
}
So in your unit test, you need to create a mock for the repository being returned from IRepositoryResolver:
private readonly Mock<IRepository> _mockRepository = new Mock<IRepository>();
Then, you need to set up the mock IRepositoryResolver to return the mock repository above:
_mockRepositoryResolver
.Setup(x => x.ResolveEstabAgeCategory())
.Returns(_mockRepository.Object);
This is to satisfy the if (repository != null) condition.
_mockRepository
.Setup(x => x.InsertUpdateEstabAgeCategoryDetail(inventoryRequest.EstabAgeCategories))
.ReturnsAsync(6);
Next, you need to set up the InsertUpdateEstabAgeCategoryDetail() method on the mock repository to return a value. This value is being returned by ProcessRequest() and then used to call CreateResponse(inventoryRequest, result) as the result parameter.
if (requestCount > 0 && resultCount < requestCount)
{
handlerResult.IsSuccess = false;
handlerResult.ErrorCode = (int)OTAErrorType.InvalidAgeCategory;
}
else if (requestCount > 0 || requestCount == resultCount)
{
handlerResult.IsSuccess = true;
handlerResult.ErrorCode = 0;
}
Now you can look at the CreateResponse method and by setting different values for inventoryRequest.EstabAgeCategories and setting up the mock _mockRepository.Setup(x => x.InsertUpdateEstabAgeCategoryDetail(inventoryRequest.EstabAgeCategories)).ReturnsAsync(6); to return different values, you can satisfy the different paths through the if statement.
CreateResponse is returning an instance of HandlerResult which in turn is being returned by Task<HandlerResult> Run. This is the returned object you want to make assertions on.
One of the unit test cases might look like this (I have not tested it myself):
[Fact]
public async Task GivenInventoryRequest_WhenRun_ThenHandlerResultReturned()
{
// arrange
var inventoryRequest = new InventoryRequest
{
EstabAgeCategories = new List<int>
{
1, 2, 3, 4, 5
}
};
_mockRepository
.Setup(x => x.InsertUpdateEstabAgeCategoryDetail(inventoryRequest.EstabAgeCategories))
.ReturnsAsync(6);
_mockRepositoryResolver
.Setup(x => x.ResolveEstabAgeCategory())
.Returns(_mockRepository.Object);
// act
var result = await _ageCategoryRequestHandler.Run(inventoryRequest);
// assert
_mockRepository
.Verify(x => x.InsertUpdateEstabAgeCategoryDetail(inventoryRequest.EstabAgeCategories), Times.Once);
Assert.True(result.Id == 0);
Assert.True(result.ErrorCode == 0);
Assert.False(result.IsSuccess);
}

OData service with multiple routes while using unbound functions

Does anyone know how to get OData v4 hosted in a .NET service to work with multiple routes?
I have the following:
config.MapODataServiceRoute("test1", "test1", GetEdmModelTest1());
config.MapODataServiceRoute("test2", "test2", GetEdmModelTest2());
Each of the GetEdmModel methods have mapped objects.
I can get to the service as following (this is working fine):
http://testing.com/test1/objects1()
http://testing.com/test2/objects2()
But if I try to call a function like the following (will not work):
[HttpGet]
[ODataRoute("test1/TestFunction1()")]
public int TestFunction1()
{ return 1; }
It will throw the following error:
The path template 'test1/TestFunction1()' on the action 'TestFunction1' in controller 'Testing' is not a valid OData path template. Resource not found for the segment 'test1'.
Yet if I remove the "MapODataServiceRoute" for "test2" so there is only one route, it all works.
How do I get this to work with multiple routes?
** I have posted a full example of the issue at the following **
https://github.com/OData/WebApi/issues/1223
** I have tried the OData version sample listed below with the following issues **
https://github.com/OData/ODataSamples/tree/master/WebApi/v4/ODataVersioningSample
I have tried the "OData Version" example before and it did not work.
It seems that unbound (unbound is the goal) does not follow the same routing rules are normal service calls.
Ex. If you download the "OData Version" example and do the following.
In V1 -> WebApiConfig.cs add
builder.Function(nameof(Controller.ProductsV1Controller.Test)).Returns<string>();
In V2 -> WebApiConfig.cs add
builder.Function(nameof(Controller.ProductsV2Controller.Test)).Returns<string>();
In V1 -> ProductsV1Controller.cs add
[HttpGet]
[ODataRoute("Test()")]
public string Test()
{ return "V1_Test"; }
In V2 -> ProductsV2Controller.cs add
[HttpGet]
[ODataRoute("Test()")]
public string Test()
{ return "V2_Test"; }
Now call it by this. " /versionbyroute/v1/Test() " and you will get "V2_Test"
The problem is that "GetControllerName" does not know how to get the controller when it is using unbound functions / actions.
This is why most sample code I have found fails when trying to "infer" the controller.
Have a look at OData Versioning Sample for a primer.
The key point of trouble is usually that the DefaultHttpControllerSelector maps controllers by local name, not fullname/namespace.
If your entity types and therefore controller names are unique across both EdmModels you will not have to do anything special, it should just work out of the box. The above sample takes advantage of this concept by forcing you to inject a string value into the physical names of the controller classes to make them unique and then in the ODataVersionControllerSelector GetControllerName is overridden to maps the incoming route to the customised controller names
If unique names for the controllers seems to hard, and you would prefer to use the full namespace (meaning your controller names logic remains standard) then you can of course implement your own logic to select the specific controller class instance when overriding DefaultHttpControllerSelector. simply override SelectController instead. This method will need to return an instance of HttpControllerDescriptor which is a bit more involved than the sample.
To show you what I mean, I will post the solution to a requirement from an older project, that was a little bit different to yours. I have a single WebAPI project that manages access to multiple databases, these databases have similar schema, many Entity names are the same which means that those controller classes will have the same names. The controllers are structured by folders/namespaces such that there is a root folder called DB, then there is a folder for each database, then the controllers are in there.
You can see that this project has many different schemas, they effectively map to versions of an evolving solution, the non-DB namespaces in this image are a mix of OData v4, v3 and standard REST apis. It is possible to get all these beasts to co-exist ;)
This override of the HttpControllerSelector inspects the runtime once to cache a list of all the controller classes, then maps the incoming route requests by matching the route prefix to the correct controller class.
/// <summary>
/// Customised controller for intercepting traffic for the DB Odata feeds.
/// Any route that is not prefixed with ~/DB/ will not be intercepted or processed via this controller
/// <remarks>Will instead be directed to the base class</remarks>
/// </summary>
public class DBODataHttpControllerSelector : DefaultHttpControllerSelector
{
private readonly HttpConfiguration _configuration;
public DBODataHttpControllerSelector(HttpConfiguration config)
: base(config)
{
_configuration = config;
}
// From: http://www.codeproject.com/Articles/741326/Introduction-to-Web-API-Versioning
private Dictionary<string, HttpControllerDescriptor> _controllerMap = null;
private List<string> _duplicates = new List<string>();
/// <summary>
/// Because we are interested in supporting nested namespaces similar to MVC "Area"s we need to
/// Index our available controller classes by the potential url segments that might be passed in
/// </summary>
/// <returns></returns>
private Dictionary<string, HttpControllerDescriptor> InitializeControllerDictionary()
{
if(_controllerMap != null)
return _controllerMap;
_controllerMap = new Dictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase);
// Create a lookup table where key is "namespace.controller". The value of "namespace" is the last
// segment of the full namespace. For example:
// MyApplication.Controllers.V1.ProductsController => "V1.Products"
IAssembliesResolver assembliesResolver = _configuration.Services.GetAssembliesResolver();
IHttpControllerTypeResolver controllersResolver = _configuration.Services.GetHttpControllerTypeResolver();
ICollection<Type> controllerTypes = controllersResolver.GetControllerTypes(assembliesResolver);
foreach (Type t in controllerTypes)
{
var segments = t.Namespace.Split(Type.Delimiter);
// For the dictionary key, strip "Controller" from the end of the type name.
// This matches the behavior of DefaultHttpControllerSelector.
var controllerName = t.Name.Remove(t.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length);
var key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}.{2}", segments[segments.Length - 2], segments[segments.Length - 1], controllerName);
// Check for duplicate keys.
if (_controllerMap.Keys.Contains(key))
{
_duplicates.Add(key);
}
else
{
_controllerMap[key] = new HttpControllerDescriptor(_configuration, t.Name, t);
}
}
// Remove any duplicates from the dictionary, because these create ambiguous matches.
// For example, "Foo.V1.ProductsController" and "Bar.V1.ProductsController" both map to "v1.products".
// CS: Ahem... thats why I've opted to go 3 levels of depth to key name, but this still applies if the duplicates are there again
foreach (string s in _duplicates)
{
_controllerMap.Remove(s);
}
return _controllerMap;
}
/// <summary>
/// Because we are interested in supporting nested namespaces we want the full route
/// to match to the full namespace (or at least the right part of it)
/// </summary>
/// <returns></returns>
private Dictionary<string, HttpControllerDescriptor> _fullControllerMap = null;
private Dictionary<string, HttpControllerDescriptor> InitializeFullControllerDictionary()
{
if(_fullControllerMap != null)
return _fullControllerMap;
_fullControllerMap = new Dictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase);
// Create a lookup table where key is "namespace.controller". The value of "namespace" is the last
// segment of the full namespace. For example:
// MyApplication.Controllers.V1.ProductsController => "V1.Products"
IAssembliesResolver assembliesResolver = _configuration.Services.GetAssembliesResolver();
IHttpControllerTypeResolver controllersResolver = _configuration.Services.GetHttpControllerTypeResolver();
ICollection<Type> controllerTypes = controllersResolver.GetControllerTypes(assembliesResolver);
foreach (Type t in controllerTypes)
{
var segments = t.Namespace.Split(Type.Delimiter);
// For the dictionary key, strip "Controller" from the end of the type name.
// This matches the behavior of DefaultHttpControllerSelector.
var controllerName = t.Name.Remove(t.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length);
var key = t.FullName;// t.Namespace + "." + controllerName;
_fullControllerMap[key] = new HttpControllerDescriptor(_configuration, t.Name, t);
}
return _fullControllerMap;
}
/// <summary>
/// Select the controllers with a simulated MVC area sort of functionality, but only for the ~/DB/ route
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public override System.Web.Http.Controllers.HttpControllerDescriptor SelectController(System.Net.Http.HttpRequestMessage request)
{
string rootPath = "db";
IHttpRouteData routeData = request.GetRouteData();
string[] uriSegments = request.RequestUri.LocalPath.Split('/');
if (uriSegments.First().ToLower() == rootPath || uriSegments[1].ToLower() == rootPath)
{
#region DB Route Selector
// If we can find a known api and a controller, then redirect to the correct controller
// Otherwise allow the standard select to work
string[] knownApis = new string[] { "tms", "srg", "cumulus" };
// Get variables from the route data.
/* support version like this:
* config.Routes.MapODataRoute(
routeName: "ODataDefault",
routePrefix: "{version}/{area}/{controller}",
model: model);
object versionName = null;
routeData.Values.TryGetValue("version", out versionName);
object apiName = null;
routeData.Values.TryGetValue("api", out apiName);
object controllerName = null;
routeData.Values.TryGetValue("controller", out controllerName);
* */
// CS: we'll just use the local path AFTER the root path
// db/tms/contact
// db/srg/contact
// Implicity parse this as
// db/{api}/{controller}
// so [0] = ""
// so [1] = "api"
// so [2] = "version" (optional)
// so [2 or 3] = "controller"
if (uriSegments.Length > 3)
{
string apiName = uriSegments[2];
if (knownApis.Contains(string.Format("{0}", apiName).ToLower()))
{
string version = "";
string controllerName = uriSegments[3];
if (controllerName.ToLower().StartsWith("v")
// and the rest of the name is numeric
&& !controllerName.Skip(1).Any(c => !Char.IsNumber(c))
)
{
version = controllerName;
controllerName = uriSegments[4];
}
// if the route has an OData item selector (#) then this needs to be trimmed from the end.
if (controllerName.Contains('('))
controllerName = controllerName.Substring(0, controllerName.IndexOf('('));
string fullName = string.Format(CultureInfo.InvariantCulture, "{0}.{1}.{2}", apiName, version, controllerName).Replace("..", ".");
// Search for the controller.
// _controllerTypes is a list of HttpControllerDescriptors
var descriptors = InitializeControllerDictionary().Where(t => t.Key.EndsWith(fullName, StringComparison.OrdinalIgnoreCase)).ToList();
if (descriptors.Any())
{
var descriptor = descriptors.First().Value;
if (descriptors.Count > 1)
{
descriptor = null;
// Assume that the version was missing, and we have implemented versioning for that controller
// If there is a row with no versioning, so no v1, v2... then use that
// if all rows are versioned, use the highest version
if (descriptors.Count(d => d.Key.Split('.').Length == 2) == 1)
descriptor = descriptors.First(d => d.Key.Split('.').Length == 2).Value;
else if (descriptors.Count(d => d.Key.Split('.').Length > 2) == descriptors.Count())
descriptor = descriptors
.Where(d => d.Key.Split('.').Length > 2)
.OrderByDescending(d => d.Key.Split('.')[1])
.First().Value;
if (descriptor == null)
throw new HttpResponseException(
request.CreateErrorResponse(HttpStatusCode.InternalServerError,
"Multiple controllers were found that match this un-versioned request."));
}
if (descriptor != null)
return descriptor;
}
if (_duplicates.Any(d => d.ToLower() == fullName.ToLower()))
throw new HttpResponseException(
request.CreateErrorResponse(HttpStatusCode.InternalServerError,
"Multiple controllers were found that match this request."));
}
}
#endregion DB Route Selector
}
else
{
// match on class names that match the route.
// So if the route is odata.tms.testController
// Then the class name must also match
// Add in an option to doing a string mapping, so that
// route otms can mapp to odata.tms
// TODO: add any other custom logic for selecting the controller that you want, alternatively try this style syntax in your route config:
//routes.MapRoute(
// name: "Default",
// url: "{controller}/{action}/{id}",
// defaults: new { controller = "Home", action = "RegisterNow", id = UrlParameter.Optional },
// namespaces: new[] { "YourCompany.Controllers" }
//);
// Because controller path mapping might be controller/navigationproperty/action
// We need to check for the following matches:
// controller.navigationproperty.actionController
// controller.navigationpropertyController
// controllerController
string searchPath = string.Join(".", uriSegments).ToLower().Split('(')[0] + "controller";
var descriptors = InitializeFullControllerDictionary().Where(t => t.Key.ToLower().Contains(searchPath)).ToList();
if (descriptors.Any())
{
var descriptor = descriptors.First().Value;
if (descriptors.Count > 1)
{
descriptor = null;
// In this mode, I think we should only ever have a single match, ready to prove me wrong?
if (descriptor == null)
throw new HttpResponseException(
request.CreateErrorResponse(HttpStatusCode.InternalServerError,
"Multiple controllers were found that match this namespace request."));
}
if (descriptor != null)
return descriptor;
}
}
return base.SelectController(request);
}
}
You can use a Custsom MapODataServiceRoute.
The below is an example from WebApiConfig.cs
The controllers are registered with the CustomMapODataServiceRoute and its a bit cumbersome having to include typeof(NameOfController) for every controller. One of my endpoints has 22 separate controllers, but thus far it's worked.
Registering Controllers - Showing two separate OData endpoints in the same project, both containing custom functions
// Continuing Education
ODataConventionModelBuilder continuingEdBuilder = new ODataConventionModelBuilder();
continuingEdBuilder.Namespace = "db_api.Models";
var continuingEdGetCourse = continuingEdBuilder.Function("GetCourse");
continuingEdGetCourse.Parameter<string>("term_code");
continuingEdGetCourse.Parameter<string>("ssts_code");
continuingEdGetCourse.Parameter<string>("ptrm_code");
continuingEdGetCourse.Parameter<string>("subj_code_prefix");
continuingEdGetCourse.Parameter<string>("crn");
continuingEdGetCourse.ReturnsCollectionFromEntitySet<ContinuingEducationCoursesDTO>("ContinuingEducationCourseDTO");
config.CustomMapODataServiceRoute(
routeName: "odata - Continuing Education",
routePrefix: "contEd",
model: continuingEdBuilder.GetEdmModel(),
controllers: new[] { typeof(ContinuingEducationController) }
);
// Active Directory OData Endpoint
ODataConventionModelBuilder adBuilder = new ODataConventionModelBuilder();
adBuilder.Namespace = "db_api.Models";
// CMS Groups
var cmsGroupFunc = adBuilder.Function("GetCMSGroups");
cmsGroupFunc.Parameter<string>("user");
cmsGroupFunc.ReturnsCollectionFromEntitySet<GenericValue>("GenericValue");
// Departments
var deptUsersFunc = adBuilder.Function("GetADDepartmentUsers");
deptUsersFunc.Parameter<string>("department");
deptUsersFunc.ReturnsCollectionFromEntitySet<ADUser>("ADUser");
var adUsersFunc = adBuilder.Function("GetADUser");
adUsersFunc.Parameter<string>("name");
adUsersFunc.ReturnsCollectionFromEntitySet<ADUser>("ADUser");
var deptFunc = adBuilder.Function("GetADDepartments");
deptFunc.ReturnsCollectionFromEntitySet<GenericValue>("GenericValue");
var instDeptFunc = adBuilder.Function("GetADInstructorDepartments");
instDeptFunc.ReturnsCollectionFromEntitySet<GenericValue>("GenericValue");
var adTitleFunc = adBuilder.Function("GetADTitles");
adTitleFunc.ReturnsCollectionFromEntitySet<GenericValue>("GenericValue");
var adOfficeFunc = adBuilder.Function("GetADOffices");
adOfficeFunc.ReturnsCollectionFromEntitySet<GenericValue>("GenericValue");
var adDistListFunc = adBuilder.Function("GetADDistributionLists");
adDistListFunc.ReturnsCollectionFromEntitySet<GenericValue>("GenericValue");
config.CustomMapODataServiceRoute(
routeName: "odata - Active Directory",
routePrefix: "ad",
model: adBuilder.GetEdmModel(),
controllers: new[] { typeof(DepartmentsController), typeof(CMSGroupsController)
});
Creating Custom Map OData Service Route
public static class HttpConfigExt
{
public static System.Web.OData.Routing.ODataRoute CustomMapODataServiceRoute(this HttpConfiguration configuration, string routeName,
string routePrefix, Microsoft.OData.Edm.IEdmModel model, IEnumerable<Type> controllers)
{
var routingConventions = ODataRoutingConventions.CreateDefault();
// Multiple Controllers with Multiple Custom Functions
routingConventions.Insert(0, new CustomAttributeRoutingConvention(routeName, configuration, controllers));
// Custom Composite Key Convention
//routingConventions.Insert(1, new CompositeKeyRoutingConvention());
return configuration.MapODataServiceRoute(routeName,
routePrefix,
model,
new System.Web.OData.Routing.DefaultODataPathHandler(),
routingConventions,
defaultHandler: System.Net.Http.HttpClientFactory.CreatePipeline( innerHandler: new System.Web.Http.Dispatcher.HttpControllerDispatcher(configuration),
handlers: new[] { new System.Web.OData.ODataNullValueMessageHandler() }));
}
}
public class CustomAttributeRoutingConvention : AttributeRoutingConvention
{
private readonly List<Type> _controllers = new List<Type> { typeof(System.Web.OData.MetadataController) };
public CustomAttributeRoutingConvention(string routeName, HttpConfiguration configuration, IEnumerable<Type> controllers)
: base(routeName, configuration)
{
_controllers.AddRange(controllers);
}
public override bool ShouldMapController(System.Web.Http.Controllers.HttpControllerDescriptor controller)
{
return _controllers.Contains(controller.ControllerType);
}
}

ServiceStack MockRequestContext CustomAuthUserSession

I'm unit testing my API service and all is well using the MockRquestContext. The calls to this.GetSession() always returns an IAuthSession, but I have a custom AuthUserSession and as far as I can tell, there's no way to create an instance of my custom AuthUserSession and add it to the mock context. Is this possible?
var service = container.Resolve<AgencyCaseService>();
service.SetResolver(new BasicResolver(container));
var context = new MockRequestContext() { ResponseContentType = ContentType.Json };
//Something like this
MyCustomAuthSession session = new MyCustomAuthSession() { set some values}
context.AuthSession = session//this doesn't exist but it's the type of thing i need to do
service.RequestContext = context;
The Session isn't on the Request Context, it requires a mixture of ICacheClient, SessionFeature and HttpRequest cookies to create.
You can look at the implementation for the way to mock it inside a Service, which shows it first tries to resolve it in a Container:
private object userSession;
protected virtual TUserSession SessionAs<TUserSession>()
{
if (userSession == null)
{
userSession = TryResolve<TUserSession>(); //Easier to mock
if (userSession == null)
userSession = Cache.SessionAs<TUserSession>(Request, Response);
}
return (TUserSession)userSession;
}
So to mock it you could just do:
container.Register(new MyCustomAuthSession() { /* set some values*/ });

Returning IQueryable but need Raven stats to insert TotalResults header

I have a WebApi method which returns an IQueryable of RavenDB documents. The caller needs to know the number of possible results (because the actual results are limited/paged).
So, I have something like this at the end of my WebApi method:
HttpContext.Current.Response.AddHeader("Total-Result-Count",
resultsStats.TotalResults.ToString())
Unfortunately, this won't work, because the IQueryable hasnt actually executed yet - so the stats will be empty.
How do I go about deferring the population of the stats response-header until AFTER the query has executed?
[UPDATE]
I attempted to apply an ActionFilter to capture the result after the controller action had executed... but it seems the ActionFilter is invoked BEFORE the IQueryable is actually enumerated...
public class CountQueryableResultsActionFilter : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext filterContext)
{
var controllerStats = filterContext.ActionContext.ControllerContext.Controller as IControllerStatistics;
System.Web.HttpContext.Current.Response.AddHeader("Total-Result-Count", controllerStats.TotalResults.ToString());
}
}
IF, I called "IQueryable.ToArray()" at the end of the WebApi method, then the Linq query gets executed immediately, it generates statistics, and everything works - but that will prevent the user from being able to apply their own OData filters etc...
Ok - I figured it out.
The following will result in only a single Raven query being issued, which returns both the result, and the result-count.
Thanks to David Ruttka for his experiments in this area. I have adapted his code to work with with RavenDb. This code will return the results, and the result-count through one database query, as RavenDB intended.
I have appended my code below - to use this, you must return IRavenQueryable<T> from your WebApi method (not IQueryable<T>). Then, appending $inlinecount=allpages to your Uri will invoke the handler. This code will not break the other OData query extensions ($take, $skip etc)
Note: This code uses the 'inline' technique, in that the statistics are returned in the message body - you could change the code to inject the stats in the header if you liked - I just chose to go with the standard way that OData works.
You could adapt this code to include any and all of the statistics that Raven generates.
Use the following code to register the handler with ASP.NET (in your Global.asax.cs)
RegistrationCode:
GlobalConfiguration.Configuration.MessageHandlers.Add(new WebApi.Extensions.InlineRavenCountHandler());
Handler code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Reflection;
using System.Net.Http.Headers;
using System.Net;
namespace WebApi.Extensions
{
public class InlineRavenCountHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (!ShouldInlineCount(request))
return base.SendAsync(request, cancellationToken);
// Otherwise, we have a continuation to work our magic...
return base.SendAsync(request, cancellationToken).ContinueWith(
t =>
{
var response = t.Result;
// Is this a response we can work with?
if (!ResponseIsValid(response)) return response;
var pagedResultsValue = this.GetValueFromObjectContent(response.Content);
Type queriedType;
// Can we find the underlying type of the results?
if (pagedResultsValue is IQueryable)
{
queriedType = ((IQueryable)pagedResultsValue).ElementType;
// we need to work with an instance of IRavenQueryable to support statistics
var genericQueryableType = typeof(Raven.Client.Linq.IRavenQueryable<>).MakeGenericType(queriedType);
if (genericQueryableType.IsInstanceOfType(pagedResultsValue))
{
Raven.Client.Linq.RavenQueryStatistics stats = null;
// register our statistics object with the Raven query provider.
// After the query executes, this object will contain the appropriate stats data
dynamic dynamicResults = pagedResultsValue;
dynamicResults.Statistics(out stats);
// Create the return object.
var resultsValueMethod =
this.GetType().GetMethod(
"CreateResultValue", BindingFlags.Instance | BindingFlags.NonPublic).MakeGenericMethod(
new[] { queriedType });
// Create the result value with dynamic type
var resultValue = resultsValueMethod.Invoke(
this, new[] { stats, pagedResultsValue });
// Push the new content and return the response
response.Content = CreateObjectContent(
resultValue, response.Content.Headers.ContentType);
return response;
}
else
return response;
}
else
return response;
});
}
private bool ResponseIsValid(HttpResponseMessage response)
{
// Only do work if the response is OK
if (response == null || response.StatusCode != HttpStatusCode.OK) return false;
// Only do work if we are an ObjectContent
return response.Content is ObjectContent;
}
private bool ShouldInlineCount(HttpRequestMessage request)
{
var queryParams = request.RequestUri.ParseQueryString();
var inlinecount = queryParams["$inlinecount"];
return string.Compare(inlinecount, "allpages", true) == 0;
}
// Dynamically invoked for the T returned by the resulting ApiController
private ResultValue<T> CreateResultValue<T>(Raven.Client.Linq.RavenQueryStatistics stats, IQueryable<T> pagedResults)
{
var genericType = typeof(ResultValue<>);
var constructedType = genericType.MakeGenericType(new[] { typeof(T) });
var ctor = constructedType
.GetConstructors().First();
var instance = ctor.Invoke(null);
var resultsProperty = constructedType.GetProperty("Results");
resultsProperty.SetValue(instance, pagedResults.ToArray(), null);
var countProperty = constructedType.GetProperty("Count");
countProperty.SetValue(instance, stats.TotalResults, null);
return instance as ResultValue<T>;
}
// We need this because ObjectContent's Value property is internal
private object GetValueFromObjectContent(HttpContent content)
{
if (!(content is ObjectContent)) return null;
var valueProperty = typeof(ObjectContent).GetProperty("Value", BindingFlags.Instance | BindingFlags.NonPublic);
if (valueProperty == null) return null;
return valueProperty.GetValue(content, null);
}
// We need this because ObjectContent's constructors are internal
private ObjectContent CreateObjectContent(object value, MediaTypeHeaderValue mthv)
{
if (value == null) return null;
var ctor = typeof(ObjectContent).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).FirstOrDefault(
ci =>
{
var parameters = ci.GetParameters();
if (parameters.Length != 3) return false;
if (parameters[0].ParameterType != typeof(Type)) return false;
if (parameters[1].ParameterType != typeof(object)) return false;
if (parameters[2].ParameterType != typeof(MediaTypeHeaderValue)) return false;
return true;
});
if (ctor == null) return null;
return ctor.Invoke(new[] { value.GetType(), value, mthv }) as ObjectContent;
}
}
public class ResultValue<T>
{
public int Count { get; set; }
public T[] Results { get; set; }
}
}
You can wrap the IQueryable and intercept the GetEnumerator. A sample of this is for example here:
http://blogs.msdn.com/b/alexj/archive/2010/03/01/tip-55-how-to-extend-an-iqueryable-by-wrapping-it.aspx. It does something a bit different but it should give you the idea.
Also - the caller can use $inlinecount=allpages in the URL to do this using the OData protocol. Although I'm not sure if WebAPI supports this query option yet.