Asp.Net Core Web API entity Framework connect to two databases - asp.net

I am doing an Asp.Net Core API and I am connecting to a two databases using EF setted in appsettings.json
"ConnectionStrings": {
"DBConnection": "Server=2679; Database=A; Trusted_Connection=true; MultipleActiveResultSets=true; Integrated Security=true;Encrypt=false;",
"DBConnection2": "Server= 2684; Database=B; Trusted_Connection=true; MultipleActiveResultSets=true; Integrated Security=true;Encrypt=false;"
}
In my Program.cs I have setted this two connections
var connectionString = (builder.Configuration.GetConnectionString("DBConnection") ?? String.Empty).Trim();
var connectionString2 = (builder.Configuration.GetConnectionString("DBConnectionAnthem") ?? String.Empty).Trim();
builder.Services.ConfigureServices(connectionString);
builder.Services.ConfigureServices(connectionString2);
I call ConfigureServices with both connections and looks like this
public static class Configure
{
public static void ConfigureServices(this IServiceCollection services, string connectionString)
{
services
.AddDbContext<CobraDbContext>(options => options.UseSqlServer(connectionString));
........
services.AddScoped<IUnitOfWork, UnitOfWork>();
}
}
}
I am using EF and I have defined my DbContext like this
public class CobraDbContext : DbContext
{
public CobraDbContext(DbContextOptions<CobraDbContext> options)
: base(options)
{
}
public DbSet<SearchResultModel> ParticipantSearch { get; set; } = null!;
....
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
}
}
From My Controller Method I call the Service.cs witch use UnitOfwork
public class ParticipantService : IParticipantService
{
private readonly ILogger<ParticipantService> _logger;
private readonly IUnitOfWork _iUnitOfwork;
public ParticipantService(ILogger<ParticipantService> logger, IUnitOfWork iUnitOfwork)
{
_logger = logger;
_iUnitOfwork = iUnitOfwork;
}
public async Task<HttpResponseMessage> Search(string participantId)
{
try
{
List<SearchResultModel>? search = await _iUnitOfwork.Participant.AAA(participantId);
return Request.CreateResponse(HttpStatusCode.OK, search);
}
catch (Exception ex)
{
}
}
From My Service I call the Repository that have a generic repository
public class ParticipantRepository : GenericRepository<SearchResultModel>, IParticipantRepository
{
private readonly CobraDbContext _db;
public ParticipantRepository(CobraDbContext db) : base(db)
{
_db = db;
}
public async Task<List<ParticipantPlanModel>?> AAA(string participantId)
{
Query participantGetByID = new();
Dictionary<string, string> dictionary = new Dictionary<string, string>();
participantGetByID.SelectFrom = " exec sp";
List<ParticipantPlanModel>? _return = await ExecuteGeneric(participantGetByID);
return _return;
}
}
I have my generic repo like this
public class GenericRepository<T> : IGenericRepository<T> where T : class
{
protected readonly CobraDbContext Context;
internal DbSet<T> dbSet;
public GenericRepository(CobraDbContext context)
{
Context = context;
dbSet = context.Set<T>();
}
public async Task<List<T>?> ExecuteGeneric(Query query)
{
// var defaultVal = default(T);
var cParameters = new SqlParameter[query.Parameters?.Count ?? 0];
if (query.Parameters != null)
{
int i = 0;
foreach (KeyValuePair<string, string> _param in query.Parameters)
{
cParameters[i] = new SqlParameter() { ParameterName = _param.Key, Value = _param.Value };
i++;
}
}
return await Context.Set<T>().FromSqlRaw(query.SelectFrom + query.Where + query.OrderBy, cParameters).ToListAsync();
}
Depending on the parameter I have to call a database or a the another. I know I can do this duplicating almost all the code... Having to DbContext and two generic Repo..
Is there a way to simplify it and not replicate most of the code?
Thanks

Related

DbContext in Service triggered by Hangfire

I have a .NET 6 Razor Pages app that triggers background tasks and then informs the user of their status via SignalR.
I'm trying to use Database1 context in the PerformBackgroundJob method, but it's disposed. What technique should I use to inject Database1 context in PerformBackgroundJob, or how else can I get this to work?
namespace Toolkat.Pages
{
public class ProcessModel : PageModel
{
private readonly Database1Context _context;
private readonly ToolkatContext _tkcontext;
private IConfiguration configuration;
private readonly IQueue _queue;
private readonly IHubContext<JobHub> _hubContext;
static ServerConnection conn;
static Server server;
static Job job;
public ProcessModel(
Database1Context context,
ToolkatContext tkcontext,
IConfiguration _configuration,
IQueue queue,
IHubContext<JobHub> hubContext)
{
_context = context;
_tkcontext = tkcontext;
configuration = _configuration;
_queue = queue;
_hubContext = hubContext;
}
public IList<CustomFileImport> CustomFileImport { get; set; } = default!;
[BindProperty]
public CustomFileImport CustomFileImportNumberTwo { get; set; } = default!;
public async Task OnGetAsync()
{
if (_context.CustomFileImports != null)
{
CustomFileImport = await _context.CustomFileImports
.Include(c => c.FileImportType)
.Include(c => c.FileImportStatus)
.Where(i => i.FileImportStatusId.Equals(1))
.ToListAsync();
}
}
public async Task<IActionResult> OnPostAsync(int[] fileImportId)
{
//Generate GUID
Guid jobId = Guid.NewGuid();
//Update FileImportItems with GUID
foreach (var id in fileImportId)
{
if (/*id == null ||*/ _context.CustomFileImports == null)
{
return NotFound();
}
var customfileimport = await _context.CustomFileImports.FirstOrDefaultAsync(m => m.FileImportId == id);
if (customfileimport == null)
{
return NotFound();
}
customfileimport.ProcessId = jobId;
await _context.SaveChangesAsync();
}
_queue.QueueAsyncTask(() => PerformBackgroundJob(jobId));
return RedirectToPage("./Result", new { jobId });
}
private async Task PerformBackgroundJob(Guid jobId /*CancellationToken cancellationToken*/)
{
await _hubContext.Clients.Group(jobId.ToString()).SendAsync("progress", "PerformBackgroundJob Started");
/*
var customFileImports = await _context.CustomFileImports
.Include(c => c.FileImportType)
.Where(i => i.ProcessId.Equals(jobId))
.ToListAsync();
*/
Debug.WriteLine("ProviderName:" + _context.Database.ProviderName);
/*
foreach (var f in customFileImports)
{
await _hubContext.Clients.Group(jobId.ToString()).SendAsync("progress", WebUtility.HtmlEncode(f.FileName));
}
*/
}
}
}
I had to combine lessons from lots of articles to figure this out. Hangfire has a nice way of approaching this.
Replace
_queue.QueueAsyncTask(() => PerformBackgroundJob(jobId));
With
BackgroundJob.Enqueue<ProcessFilesService>(x => x.DoWork());
Passing dependencies
and create this class
public class ProcessFilesService
{
IServiceProvider _serviceProvider;
public ProcessFilesService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void DoWork()
{
using var scope = _serviceProvider.CreateScope();
var ctx = scope.ServiceProvider.GetRequiredService<MyDatabaseContext>();
using var hubScope = _serviceProvider.CreateScope();
var _hubContext = hubScope.ServiceProvider.GetRequiredService<JobHub>();
Debug.WriteLine(ctx.Database.ProviderName);
}
}
Hmm...I didn't need to register it as a service in program.cs and it appears to still be working. Will have to learn more about that.

unit text mock dbContext

I try to do a unitary test on a repository that returns a Ienumerable. But I have the next mistake:
System.AggregateException : One or more errors occurred. (The source IQueryable doesn't implement IAsyncEnumerable<myNamespace.DTO.UserDTO>. Only sources that implement IAsyncEnumerable can be used for Entity Framework asynchronous operations.)
---- System.InvalidOperationException : The source IQueryable doesn't implement IAsyncEnumerable<myNamespace.DTO.UserDTO>. Only sources that implement IAsyncEnumerable can be used for Entity Framework asynchronous operations.
This my unit test :
//Arrange
var mockSet = Substitute.For<DbSet<User>, IQueryable<User>, IDbAsyncEnumerable<User>>();
((IDbAsyncEnumerable<User>)mockSet).GetAsyncEnumerator()
.Returns(new TestDbAsyncEnumerator<User>(GetUserList().AsQueryable().GetEnumerator()));
((IQueryable<User>)mockSet).Provider.Returns(new TestDbAsyncQueryProvider<User>(GetUserList().AsQueryable().Provider));
((IQueryable<User>)mockSet).Expression.Returns(GetUserList().AsQueryable().Expression);
((IQueryable<User>)mockSet).ElementType.Returns(GetUserList().AsQueryable().ElementType);
((IQueryable<User>)mockSet).GetEnumerator().Returns(GetUserList().AsQueryable().GetEnumerator());
var mockContext = Substitute.For<IMyContext>();
mockContext.Users.Returns(mockSet);
//Act
CancellationToken cancellationToken = new CancellationToken();
UserRepository userRepository = new UserRepository(mockContext);
var users = userRepository.GetListAsync(cancellationToken).Result;
//Assert
Assert.NotNull(users);
My repo I want to test :
public async Task<IEnumerable<UserDto>> GetListAsync(CancellationToken cancellationToken)
{
return await _myContext.Users.Select(u => new UserDto
{
Id = u.Id,
FistName = u.FistName ,
LastName = u.LastName
}).ToListAsync(cancellationToken);
}
What is the problem ?
As covered in the OP comments the doco you're referring to is for EF, not EFCore. You need to implement a different set of interfaces.
The usual advice is to avoid mocking the DbContext however in this case you probably need to as the async operations aren't supported by the in-memory provider. I'm not sure if SQLite supports them. EntityFrameworkCore.Testing should handle this case (disclaimer, I am the author), but you'd need to use your context implementation rather than an interface.
The most common way to get this working is to create implementations of the async interfaces, in the same manner as the EF doco but for EFCore. You'll find most EFCore mocking libraries will do it this way:
public class TestAsyncEnumerable<T> : IAsyncEnumerable<T>, IOrderedQueryable<T>
{
private readonly IEnumerable<T> _enumerable;
private readonly IQueryable<T> _queryable;
public TestAsyncEnumerable(IEnumerable<T> enumerable)
{
_enumerable = enumerable;
_queryable = _enumerable.AsQueryable();
ElementType = _queryable.ElementType;
Expression = _queryable.Expression;
Provider = new TestAsyncQueryProvider<T>(_queryable);
}
public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = new CancellationToken())
{
return new TestAsyncEnumerator<T>(_queryable);
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return _enumerable.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _enumerable.GetEnumerator();
}
public Type ElementType { get; }
public Expression Expression { get; }
public IQueryProvider Provider { get; }
}
public class TestAsyncEnumerator<T> : IAsyncEnumerator<T>
{
private readonly IEnumerator<T> _enumerator;
public TestAsyncEnumerator(IEnumerable<T> enumerable)
{
_enumerator = enumerable.GetEnumerator();
}
public ValueTask DisposeAsync()
{
return new ValueTask();
}
public ValueTask<bool> MoveNextAsync()
{
return new ValueTask<bool>(_enumerator.MoveNext());
}
public T Current => _enumerator.Current;
}
public class TestAsyncQueryProvider<T> : IAsyncQueryProvider
{
public TestAsyncQueryProvider(IQueryable<T> source)
{
Source = source;
}
private IQueryable<T> Source { get; }
public IQueryable CreateQuery(Expression expression)
{
throw new NotImplementedException();
}
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return new TestAsyncEnumerable<TElement>(Source.Provider.CreateQuery<TElement>(expression));
}
public object Execute(Expression expression)
{
throw new NotImplementedException();
}
public TResult Execute<TResult>(Expression expression)
{
throw new NotImplementedException();
}
public TResult ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken = new CancellationToken())
{
throw new NotImplementedException();
}
}
This isn't a complete implementation, just what's needed to solve the OP case. The important bit is this line:
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return new TestAsyncEnumerable<TElement>(Source.Provider.CreateQuery<TElement>(expression));
}
This is what is going to allow the projection to work with the async operation.
Working example:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using AutoFixture;
using KellermanSoftware.CompareNetObjects;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query.Internal;
using NSubstitute;
using NUnit.Framework;
namespace Question62783423
{
public class Tests
{
[Test]
public void Test1()
{
var fixture = new Fixture();
var users = new TestAsyncEnumerable<User>(fixture.CreateMany<User>());
//Arrange
var mockSet = Substitute.For<DbSet<User>, IQueryable<User>, IAsyncEnumerable<User>>();
((IAsyncEnumerable<User>) mockSet).GetAsyncEnumerator().Returns(users.GetAsyncEnumerator());
((IQueryable<User>) mockSet).Provider.Returns(users.Provider);
((IQueryable<User>) mockSet).Expression.Returns(users.Expression);
((IQueryable<User>) mockSet).ElementType.Returns(users.ElementType);
((IQueryable<User>) mockSet).GetEnumerator().Returns(((IQueryable<User>) users).GetEnumerator());
var mockContext = Substitute.For<IMyContext>();
mockContext.Users.Returns(mockSet);
//Act
var cancellationToken = new CancellationToken();
var userRepository = new UserRepository(mockContext);
var result1 = userRepository.GetListAsync(cancellationToken).Result;
var result2 = userRepository.GetListAsync(cancellationToken).Result;
var comparer = new CompareLogic();
comparer.Config.IgnoreCollectionOrder = true;
comparer.Config.IgnoreObjectTypes = true;
var comparisonResult1 = comparer.Compare(users, result1);
var comparisonResult2 = comparer.Compare(users, result2);
Assert.That(comparisonResult1.Differences.Any(), Is.False);
Assert.That(comparisonResult2.Differences.Any(), Is.False);
}
}
}
public class TestAsyncEnumerable<T> : IAsyncEnumerable<T>, IOrderedQueryable<T>
{
private readonly IEnumerable<T> _enumerable;
private readonly IQueryable<T> _queryable;
public TestAsyncEnumerable(IEnumerable<T> enumerable)
{
_enumerable = enumerable;
_queryable = _enumerable.AsQueryable();
ElementType = _queryable.ElementType;
Expression = _queryable.Expression;
Provider = new TestAsyncQueryProvider<T>(_queryable);
}
public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = new CancellationToken())
{
return new TestAsyncEnumerator<T>(_queryable);
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return _enumerable.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _enumerable.GetEnumerator();
}
public Type ElementType { get; }
public Expression Expression { get; }
public IQueryProvider Provider { get; }
}
public class TestAsyncEnumerator<T> : IAsyncEnumerator<T>
{
private readonly IEnumerator<T> _enumerator;
public TestAsyncEnumerator(IEnumerable<T> enumerable)
{
_enumerator = enumerable.GetEnumerator();
}
public ValueTask DisposeAsync()
{
return new ValueTask();
}
public ValueTask<bool> MoveNextAsync()
{
return new ValueTask<bool>(_enumerator.MoveNext());
}
public T Current => _enumerator.Current;
}
public class TestAsyncQueryProvider<T> : IAsyncQueryProvider
{
public TestAsyncQueryProvider(IQueryable<T> source)
{
Source = source;
}
private IQueryable<T> Source { get; }
public IQueryable CreateQuery(Expression expression)
{
throw new NotImplementedException();
}
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return new TestAsyncEnumerable<TElement>(Source.Provider.CreateQuery<TElement>(expression));
}
public object Execute(Expression expression)
{
throw new NotImplementedException();
}
public TResult Execute<TResult>(Expression expression)
{
throw new NotImplementedException();
}
public TResult ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken = new CancellationToken())
{
throw new NotImplementedException();
}
}
public class User
{
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class UserDto
{
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public interface IMyContext
{
DbSet<User> Users { get; set; }
}
public class UserRepository
{
private readonly IMyContext _myContext;
public UserRepository(IMyContext myContext)
{
_myContext = myContext;
}
public async Task<IEnumerable<UserDto>> GetListAsync(CancellationToken cancellationToken)
{
return await _myContext.Users.Select(u => new UserDto { Id = u.Id, FirstName = u.FirstName, LastName = u.LastName }).ToListAsync(cancellationToken);
}
}

How can I return a response in ASP.NET Core MVC middleware using MVC's content negotiation?

I have some ASP.NET Core MVC middleware to catch unhandled exceptions that I would like to return a response from.
While it is easy to just httpContext.Response.WriteAsync to write a string and e.g. use JsonSerializer to serialise an object to a string, I would like to use the standard serialisation settings and content negotiation so that if I change my default output formatting to XML or a text/xml accept header is sent when I have multiple output formatters configured then XML is returned, as it does if I return an ObjectResult from a controller.
Does anyone know how this can be achieved in middleware?
Here is my code so far which only writes JSON:
public class UnhandledExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly IOutputFormatter _outputFormatter;
private readonly IHttpResponseStreamWriterFactory _streamWriterFactory;
public UnhandledExceptionMiddleware(RequestDelegate next, JsonOutputFormatter outputFormatter, IHttpResponseStreamWriterFactory streamWriterFactory)
{
_next = next;
_outputFormatter = outputFormatter;
_streamWriterFactory = streamWriterFactory;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private async Task HandleExceptionAsync(HttpContext context, Exception exception)
{
var error = new ErrorResultModel("Internal Server Error", exception.Message, exception.StackTrace);
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
await _outputFormatter.WriteAsync(new OutputFormatterWriteContext(context, _streamWriterFactory.CreateWriter, typeof(ErrorResultModel), error));
}
}
where ErrorResultModel is defined as:
public class ErrorResultModel
{
public string ResultMessage { get; };
public string ExceptionMessage { get; };
public string ExceptionStackTrace { get; };
public ErrorResultModel(string resultMessage, string exceptionMessage, string exceptionStackTrace)
{
ResultMessage = resultMessage;
ExceptionMessage = exceptionMessage;
ExceptionStackTrace = exceptionStackTrace;
}
}
This is not possible in ASP.NET Core 2.0 MVC.
This will be possible in 2.1:
public static class HttpContextExtensions
{
private static readonly RouteData EmptyRouteData = new RouteData();
private static readonly ActionDescriptor EmptyActionDescriptor = new ActionDescriptor();
public static Task WriteResultAsync<TResult>(this HttpContext context, TResult result)
where TResult : IActionResult
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var executor = context.RequestServices.GetService<IActionResultExecutor<TResult>>();
if (executor == null)
{
throw new InvalidOperationException($"No result executor for '{typeof(TResult).FullName}' has been registered.");
}
var routeData = context.GetRouteData() ?? EmptyRouteData;
var actionContext = new ActionContext(context, routeData, EmptyActionDescriptor);
return executor.ExecuteAsync(actionContext, result);
}
}
public class Program : StartupBase
{
public static Task Main(string[] args)
{
return BuildWebHost(args).RunAsync();
}
public static IWebHost BuildWebHost(string[] args)
{
return new WebHostBuilder().UseStartup<Program>().UseKestrel().Build();
}
public override void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore().AddJsonFormatters();
}
public override void Configure(IApplicationBuilder app)
{
app.Use((ctx, next) =>
{
var model = new Person("Krisian", "Hellang");
var result = new ObjectResult(model);
return ctx.WriteResultAsync(result);
});
}
}
public class Person
{
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
public string FirstName { get; }
public string LastName { get; }
}

Unit Test issues with Entity FrameWork (nullable values)

im trying to implement a uniTest for my application so when i tried to get User by ID value in my application it's work fine, but when i tried to do the same scenario from my unit test class i always get nullable result even if the ID value is correct :
Class AccountController : ApiController
{
private UserService _UserService = null;
public AccountController()
{
_UserService = new UserService();
}
[AllowAnonymous]
[Route("test")]
public IHttpActionResult test()
{
var user = _UserService.getUserById(1); //user --> not null;
}
}
but when i tried a UnitTest Script
[TestClass]
public class userServiceTest
{
private UserService _UserService = null;
public userServiceTest()
{
_UserService = new UserService();
}
[TestMethod]
public void checkUserCase1()
{
var user = _UserService.getUserById(1); //user is null value !!!;
}
}
User Service :
public class UserService
{
private GenericRepository<User> _UserRepository = null;
public UserService()
{
_UserRepository = new GenericRepository<User>();
}
public User getUserById(int id)
{
return _UserRepository.Find(x => x.Id == id).FirstOrDefault();
}
}
The Generic Repository
public class GenericRepository<T> : IGenericRepository<T> where T : class
{
private MyDbContext db = null;
private DbSet<T> table = null;
public IEnumerable<T> Find(Expression<Func<T, bool>> predicate)
{
return table.Where(predicate);
}
}
IGeneric :
public interface IGenericRepository<T> where T : class
{
IEnumerable<T> SelectAll();
T SelectByID(object id);
void Insert(T obj);
void Update(T obj);
void Delete(object id);
void Save();
IEnumerable<T> Find(Expression<Func<T, bool>> predicate);
}
My DB Context :
public class MyDbContext : DbContext
{
public MyDbContext()
: base("AuthWebApiDb")
{
Database.SetInitializer<MyDbContext>(new MyDbInitializer());
}
public DbSet<User> Users { get; set; }
}
I have Two Project : One is the simple project, the second is the Unit Test
Check if EF is innstalled in your UnitTest project.
Put the connection string in the app.config file in the unitest project.
Thank's #Stewart_T

NHibernate in Web API ASP.NET: No session bound to the current context

I'm new to NHibernate and trying to use it in ASP.NET WEB API. Firstly I used it successfully with one table named "Category" which the controller class is as follow:
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using TestMVCProject.Web.Api.HttpFetchers;
using TestMVCProject.Web.Api.Models;
using TestMVCProject.Web.Api.TypeMappers;
using TestMVCProject.Web.Common;
//using TestMVCProject.Web.Common.Security;
using NHibernate;
namespace TestMVCProject.Web.Api.Controllers
{
[LoggingNHibernateSession]
public class CategoryController : ApiController
{
private readonly ISession _session;
private readonly ICategoryMapper _categoryMapper;
private readonly IHttpCategoryFetcher _categoryFetcher;
public CategoryController(
ISession session,
ICategoryMapper categoryMapper,
IHttpCategoryFetcher categoryFetcher)
{
_session = session;
_categoryMapper = categoryMapper;
_categoryFetcher = categoryFetcher;
}
public IEnumerable<Category> Get()
{
return _session
.QueryOver<Data.Model.Category>()
.List()
.Select(_categoryMapper.CreateCategory)
.ToList();
}
public Category Get(long id)
{
var category = _categoryFetcher.GetCategory(id);
return _categoryMapper.CreateCategory(category);
}
public HttpResponseMessage Post(HttpRequestMessage request, Category category)
{
var modelCategory = new Data.Model.Category
{
Description = category.Description,
CategoryName = category.CategoryName
};
_session.Save(modelCategory);
var newCategory = _categoryMapper.CreateCategory(modelCategory);
//var href = newCategory.Links.First(x => x.Rel == "self").Href;
var response = request.CreateResponse(HttpStatusCode.Created, newCategory);
//response.Headers.Add("Location", href);
return response;
}
public HttpResponseMessage Delete()
{
var categories = _session.QueryOver<Data.Model.Category>().List();
foreach (var category in categories)
{
_session.Delete(category);
}
return new HttpResponseMessage(HttpStatusCode.OK);
}
public HttpResponseMessage Delete(long id)
{
var category = _session.Get<Data.Model.Category>(id);
if (category != null)
{
_session.Delete(category);
}
return new HttpResponseMessage(HttpStatusCode.OK);
}
public Category Put(long id, Category category)
{
var modelCateogry = _categoryFetcher.GetCategory(id);
modelCateogry.CategoryName = category.CategoryName;
modelCateogry.Description = category.Description;
_session.SaveOrUpdate(modelCateogry);
return _categoryMapper.CreateCategory(modelCateogry);
}
}
}
But when I add The "Product" table which has a foreign key of the Category table, the product controller doesn't work and throws below exception:
No session bound to the current context
ProductController class is as follow:
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using TestMVCProject.Web.Api.HttpFetchers;
using TestMVCProject.Web.Api.Models;
using TestMVCProject.Web.Api.TypeMappers;
using TestMVCProject.Web.Common;
//using TestMVCProject.Web.Common.Security;
using NHibernate;
namespace TestMVCProject.Web.Api.Controllers
{
[LoggingNHibernateSession]
public class ProductController : ApiController
{
private readonly ISession _session;
private readonly IProductMapper _productMapper;
private readonly IHttpProductFetcher _productFetcher;
public ProductController(
ISession session,
IProductMapper productMapper,
IHttpProductFetcher productFetcher)
{
_session = session;
_productMapper = productMapper;
_productFetcher = productFetcher;
}
public IEnumerable<Product> Get()
{
return _session
.QueryOver<Data.Model.Product>()
.List()
.Select(_productMapper.CreateProduct)
.ToList();
}
public Product Get(long id)
{
var product = _productFetcher.GetProduct(id);
return _productMapper.CreateProduct(product);
}
public HttpResponseMessage Post(HttpRequestMessage request, Product product)
{
var modelProduct = new Data.Model.Product
{
Description = product.Description,
ProductName = product.ProductName
};
_session.Save(modelProduct);
var newProduct = _productMapper.CreateProduct(modelProduct);
//var href = newproduct.Links.First(x => x.Rel == "self").Href;
var response = request.CreateResponse(HttpStatusCode.Created, newProduct);
//response.Headers.Add("Location", href);
return response;
}
public HttpResponseMessage Delete()
{
var categories = _session.QueryOver<Data.Model.Product>().List();
foreach (var product in categories)
{
_session.Delete(product);
}
return new HttpResponseMessage(HttpStatusCode.OK);
}
public HttpResponseMessage Delete(long id)
{
var product = _session.Get<Data.Model.Product>(id);
if (product != null)
{
_session.Delete(product);
}
return new HttpResponseMessage(HttpStatusCode.OK);
}
public Product Put(long id, Product product)
{
var modelProduct = _productFetcher.GetProduct(id);
modelProduct.ProductName = product.ProductName;
modelProduct.Description = product.Description;
_session.SaveOrUpdate(modelProduct);
return _productMapper.CreateProduct(modelProduct);
}
}
}
and the mapping class for Product table:
using TestMVCProject.Data.Model;
using FluentNHibernate.Mapping;
namespace TestMVCProject.Data.SqlServer.Mapping
{
public class ProductMap : ClassMap<Product>
{
public ProductMap()
{
Id(x => x.ProductId);
Map(x => x.ProductName).Not.Nullable();
Map(x => x.Description).Nullable();
Map(x => x.CreateDate).Not.Nullable();
Map(x => x.Price).Not.Nullable();
References<Category>(x => x.CategoryId).Not.Nullable();
}
}
}
What is wrong?
Your snippets are missing the way, how the ISessionFactory is created and how ISession is passed into your controllers... You should follow this really comprehensive story (by Piotr Walat):
NHibernate session management in ASP.NET Web API
Where you can see that we, can use 2.3. Contextual Sessions:
NHibernate.Context.WebSessionContext - stores the current session in HttpContext. You are responsible to bind and unbind an ISession instance with static methods of class CurrentSessionContext.
The configuration
<session-factory>
..
<property name="current_session_context_class">web</property>
</session-factory>
In the article you can check that we need at the app start initialize factory (just an extract):
public class WebApiApplication : System.Web.HttpApplication
{
private void InitializeSessionFactory() { ... }
protected void Application_Start()
{
InitializeSessionFactory();
...
Next we should create some AOP filter (just an extract):
public class NhSessionManagementAttribute : ActionFilterAttribute
{
...
public override void OnActionExecuting(HttpActionContext actionContext)
{
// init session
var session = SessionFactory.OpenSession();
...
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
// close session
...
session = CurrentSessionContext.Unbind(SessionFactory);
}
For more details check the source mentioned above
Your approach of passing the session to the constructor of the controller factory does not seems to be working, there are a few ways to do this
1. Using dependency injection
If you are using a dependency injection framework, you have to configure controller so that it's constructed per request, it should looks like this (I have used the code for Ninject)
Step 1 - setup the session for injection
public class DIModule : NinjectModule
{
public override void Load()
{
this.Bind<ISessionFactory>()... bind to the session factory
this.Bind<ISession>().ToMethod(ctx => ctx.Kernel.Get<ISessionFactory>().OpenSession())
.InRequestScope();
}
private ISession CreateSessionProxy(IContext ctx)
{
var session = (ISession)this.proxyGenerator.CreateInterfaceProxyWithoutTarget(typeof(ISession), new[] { typeof(ISessionImplementor) }, ctx.Kernel.Get<SessionInterceptor>());
return session;
}
}
Step 2 - Create the controller factory so that it will inject the session when resolving
public class NinjectControllerFactory : DefaultControllerFactory, IDependencyResolver
{
private IDependencyResolver _defaultResolver;
public NinjectControllerFactory(IDependencyResolver defaultResolver)
{
_defaultResolver = defaultResolver;
}
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
return controllerType == null
? null
: (IController)DependencyKernel.Kernel.Get(controllerType);
}
public IDependencyScope BeginScope()
{
return this;
}
public object GetService(Type serviceType)
{
try
{
return DependencyKernel.Kernel.Get(serviceType);
}
catch (Exception)
{
return GetService(serviceType);
}
}
public IEnumerable<object> GetServices(Type serviceType)
{
try
{
object item = DependencyKernel.Kernel.Get(serviceType);
return new List<object>() {item};
}
catch (Exception)
{
return GetServices(serviceType);
}
}
public void Dispose()
{
}
}
Step 3 - Register the controller factory
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
var factory = new NinjectControllerFactory(GlobalConfiguration.Configuration.DependencyResolver);
ControllerBuilder.Current.SetControllerFactory(factory);
GlobalConfiguration.Configuration.DependencyResolver = factory;
}
}
Now what will happen is that when your controller is created it will inject the a new NH session per each request.
2. Using a filter
This is much simpler, but you may need to change your controllers a bit this to work,
Step 1 - Setup the correct session context for the factory
_sessionFactory = CreateConfiguration()
.ExposeConfiguration(c => c.SetProperty("current_session_context_class","web"))
.BuildSessionFactory();
Step 2 - Create the filter
public class SessionPerRequestAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var session = SessionFactory.OpenSession();
NHibernate.Context.CurrentSessionContext.Bind(session);
base.OnActionExecuting(actionContext);
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
var session = SessionFactory.GetCurrentSession();
session.Flush();
session.Clear();
session.Close();
base.OnActionExecuted(actionExecutedContext);
}
}
Step 3 - Register the filter in global configuration
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
//Do other config here
config.Filters.Add(new SessionPerRequestAttribute());
}
}
Step 4 - Modify your controller a bit,
public class CategoryController : ApiController
{
private readonly ICategoryMapper _categoryMapper;
private readonly IHttpCategoryFetcher _categoryFetcher;
public CategoryController(
ICategoryMapper categoryMapper,
IHttpCategoryFetcher categoryFetcher)
{
_categoryMapper = categoryMapper;
_categoryFetcher = categoryFetcher;
}
public IEnumerable<Category> Get()
{
var session = SessionFactory.GetCurrentSession();
return session
.QueryOver<Data.Model.Category>()
.List()
.Select(_categoryMapper.CreateCategory)
.ToList();
}
}
Here what happens is, when a request comes it will create a new session and it is bound to the request context and same is used for the web API method.

Resources