I have a MVC5 application and set my hibernate stuff up like that:
public class PersistenceInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
//Nhibernate session factory
Component.For<ISessionFactory>().UsingFactoryMethod(CreateNhSessionFactory).LifeStyle.Singleton,
Component.For<ISession>().UsingFactoryMethod(k => k.Resolve<ISessionFactory>().OpenSession()).LifestylePerWebRequest(),
//All repoistories
Classes.FromAssembly(Assembly.GetAssembly(typeof(HdtRepository))).InSameNamespaceAs<HdtRepository>().WithService.DefaultInterfaces().LifestyleTransient()
);
}
and my base repository looks like that:
public abstract class RepositoryBase<TEntity, TPrimaryKey> : IRepository<TEntity, TPrimaryKey> where TEntity : Entity<TPrimaryKey>
{
/// <summary>
/// Gets the NHibernate session object to perform database operations.
/// </summary>
public ISession Session { get; set; }
/// <summary>
/// Used to get a IQueryable that is used to retrive object from entire table.
/// </summary>
/// <returns>IQueryable to be used to select entities from database</returns>
public IQueryable<TEntity> GetAll()
{
return Session.Query<TEntity>();
}
/// <summary>
/// Gets an entity.
/// </summary>
/// <param name="key">Primary key of the entity to get</param>
/// <returns>Entity</returns>
public TEntity Get(TPrimaryKey key)
{
return Session.Get<TEntity>(key);
}
/// <summary>
/// Inserts a new entity.
/// </summary>
/// <param name="entity">Entity</param>
public void Insert(TEntity entity)
{
Session.Save(entity);
}
/// <summary>
/// Updates an existing entity.
/// </summary>
/// <param name="entity">Entity</param>
public void Update(TEntity entity)
{
Session.Update(entity);
}
/// <summary>
/// Deletes an entity.
/// </summary>
/// <param name="id">Id of the entity</param>
public void Delete(TPrimaryKey id)
{
Session.Delete(Session.Load<TEntity>(id));
}
}
Everything works corret when I Insert a entity.
Update is onyl working when I add a Session.Flush() to my Update method.
This is the mehtod which is called from Ajax and performs insert or update.
[Authorize]
[ValidateInput(false)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public JsonNetResult CreateOrUpdateTimeRecord(TimeRecord tr)
{
TimeRecord trLocal;
if (tr.Id == -1 || tr.Id == 0)
{
trLocal = new TimeRecord();
trLocal.Description = tr.Description;
trLocal.StartTime = tr.StartTime;
trLocal.EndTime = tr.EndTime;
trLocal.User = _userRepo.Get(tr.User.Id);
trLocal.Hdt = _hdtRepo.Get(tr.Hdt.Id);
_timeRepo.Insert(trLocal);
}
else
{
trLocal = _timeRepo.Get(tr.Id);
trLocal.Description = tr.Description;
trLocal.StartTime = tr.StartTime;
trLocal.EndTime = tr.EndTime;
_timeRepo.Update(trLocal);
}
return new JsonNetResult() { Data = trLocal};
}
I dont understand why this works for insert and not for Update.
Do I have to care about transaction and opening/closing Sessions?
I thought that "LifestylePerWebRequest" would do that for me.
Cheers,
Stefan
:edit: (my initial answer was partially wrong)
You implementation should actually work fine (just tested it).
Though I can reproduce the behavior that updates are not flushed although the session object gets disposed correctly
The LifestylePerWebRequest actually takes care of that just fine, it will dispose the session whenever the request ends. Therefore you had to add the request handle to the web.config etc... So the windsor stuff is perfectly aware of the fact that ISession is disposable and that it has to dispose it...
This is because of the the FlushMode of the session.
The default mode is Auto, which might not be really what you want because you cannot rely on the changes getting stored (especially with update calls).
You can either change the FlushMode of the created session object, or, and this is what I recommend, use transactions.
Lets look at the following example:
var session = container.Resolve<ISession>();
var obj = new Paper()
{
Author = "Author",
Description = "Description",
};
session.Save(obj);
obj.Author = "Author2";
session.Update(obj);
In this case, the update will never be flushed/stored in the database. This is the behavior you currently have I guess.
Now lets add a transaction around it:
var session = container.Resolve<ISession>();
using (var transaction = session.BeginTransaction())
{
var obj = new Paper()
{
Author = "Author",
Description = "Description",
};
session.Save(obj);
obj.Author = "Author2";
session.Update(obj);
transaction.Commit();
}
Now the changes will be saved for sure.
Related
I've set up my menu using MVCSiteMap and I have this node:
<mvcSiteMapNode title="Courses Form" controller="Booking" action="Course" roles="CORLIC, VIEWCOBO"/>
I'm trying to enforce that this node must have roles "CORLIC" AND "VIEWCOBO" for it to be visible but of course this means that it will be displayed if the user has either of the above.
Is this possible?
Thanks.
The roles attribute is for interoperability with ASP.NET and should not be used in an MVC-only application.
For MVC, if you are already defining the AuthorizeAttribute on your controller actions, MvcSiteMapProvider will automatically pick them up and hide the matching nodes accordingly if security trimming is enabled.
[Authorize]
public ActionResult Course()
{
return View();
}
[Authorize]
[HttpPost]
public ActionResult Course(CourseModel model)
{
if (ModelState.IsValid)
{
// Implementation omitted
}
// If we got this far, something failed, redisplay form
return View(model);
}
The default AuthorizeAttribute accepts roles, but it works in the same way as the roles attribute - that is, any role that the user is in will cause it to succeed.
However, you could inherit AuthorizeAttribute yourself and override the IsAuthorized method to change the logic as needed.
public class SpecialAuthorizeAttribute : AuthorizeAttribute
{
private string _requiredRoles;
private string[] _requiredRolesSplit = new string[0];
/// <summary>
/// Gets or sets the required roles. The user must be a member of all roles for it to succeed.
/// </summary>
/// <value>
/// The roles string.
/// </value>
/// <remarks>Multiple role names can be specified using the comma character as a separator.</remarks>
public string RequiredRoles
{
get { return _requiredRoles ?? String.Empty; }
set
{
_requiredRoles = value;
_requiredRolesSplit = SplitString(value);
}
}
/// <summary>
/// Determines whether access for this particular request is authorized. This method uses the user <see cref="IPrincipal"/>
/// returned via <see cref="HttpRequestContext.Principal"/>. Authorization is denied if the user is not authenticated,
/// the user is not in the authorized group of <see cref="Users"/> (if defined), or if the user is not in any of the authorized
/// <see cref="Roles"/> (if defined).
/// </summary>
/// <param name="actionContext">The context.</param>
/// <returns><c>true</c> if access is authorized; otherwise <c>false</c>.</returns>
protected override bool IsAuthorized(HttpActionContext actionContext)
{
if (actionContext == null)
{
throw new ArgumentNullException("actionContext");
}
IPrincipal user = actionContext.ControllerContext.RequestContext.Principal;
if (user == null || user.Identity == null || !user.Identity.IsAuthenticated)
{
return false;
}
// Ensure all of the roles in RequiredRoles are present.
if (_requiredRolesSplit.Length > 0 && !_requiredRolesSplit.All(user.IsInRole))
{
return false;
}
// Call the base class to check the users and roles there.
return base.IsAuthorized(actionContext);
}
/// <summary>
/// Splits the string on commas and removes any leading/trailing whitespace from each result item.
/// </summary>
/// <param name="original">The input string.</param>
/// <returns>An array of strings parsed from the input <paramref name="original"/> string.</returns>
internal static string[] SplitString(string original)
{
if (String.IsNullOrEmpty(original))
{
return new string[0];
}
var split = from piece in original.Split(',')
let trimmed = piece.Trim()
where !String.IsNullOrEmpty(trimmed)
select trimmed;
return split.ToArray();
}
}
Then you can specify which roles are required by using the new property.
[SpecialAuthorize(RequiredRoles = "CORLIC, VIEWCOBO")]
public ActionResult Course()
{
return View();
}
[SpecialAuthorize(RequiredRoles = "CORLIC, VIEWCOBO")]
[HttpPost]
public ActionResult Course(CourseModel model)
{
if (ModelState.IsValid)
{
// Implementation omitted
}
// If we got this far, something failed, redisplay form
return View(model);
}
Another possible option is to use FluentSecurity as shown here. For FluentSecurity v2.0 to work with MvcSiteMapProvider, you need to copy the HandleSecurityAttribute code into your project and change it to inherit from AuthorizeAttribute instead of Attribute, then use it as specified in the FluentSecurity documentation.
I have a class that I proxy it with Castle Dynamic Proxy. I want to add some custom Attributes to proxy methods (which is not defined in proxied class). Is this possible.
I want this because I want to generate ASP.NET Web API layer for my application's Service Layer. I proxied services (with inheriting from ApiController and additional IMyService interfaces), it works great but I want to add WebAPI specific attributes to this newly created Dynamic class, thus Web API framework can read them.
EDIT:
I want to explain detailed if someone want to know what I want actually.
public interface IMyService
{
IEnumerable<MyEntity> GetAll();
}
public class MyServiceImpl : IMyService
{
public IEnumerable<MyEntity> GetAll()
{
return new List<MyEntity>(); //TODO: Get from database!
}
}
public class MyServiceApiController : ApiController,IMyService
{
private readonly IMyService _myService;
public MyServiceApiController(IMyService myService)
{
_myService = myService;
}
public IEnumerable<MyEntity> GetAll()
{
return _myService.GetAll();
}
}
Think that I have a IMyService which is implemented by MyServiceImpl. And I want to make a Api controller to be able to use this service from web.
But as you see, api controller is just a proxy for real service. So, why I should write it? I can dynamically create it using castle windsor.
This is my idea and almost done it in my new project (https://github.com/hikalkan/aspnetboilerplate). But what if I need to add some attribute (such as Authorize) to GetAll method of the api controller. I cant directly add since there is no such a class, it's castle dynamic proxy.
So, beside this problem. I want to know if it's possible to add a attribute to a method of a synamic proxy class.
See again that project
https://github.com/aspnetboilerplate/aspnetboilerplate/issues/55
I also want to know how, so that I can define RoutePrefix on IService and Route on Action.
Fortunately, I finally know how to define custom attributes for proxy.
public class CustomProxyFactory : DefaultProxyFactory
{
#region Overrides of DefaultProxyFactory
protected override void CustomizeOptions(ProxyGenerationOptions options, IKernel kernel, ComponentModel model, object[] arguments)
{
var attributeBuilder = new CustomAttributeBuilder(typeof(DescriptionAttribute).GetConstructor(new[] { typeof(string) }), new object[] { "CustomizeOptions" });
options.AdditionalAttributes.Add(attributeBuilder);
}
#endregion
}
/// <summary>
/// 用户信息服务
/// </summary>
[Description("IUserInfoService")]
public interface IUserInfoService
{
/// <summary>
/// 获取用户信息
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
[Description("IUserInfoService.GetUserInfo")]
UserInfo GetUserInfo([Description("IUserInfoService.GetUserInfo name")] string name);
}
/// <summary>
/// 用户信息服务
/// </summary>
[Description("UserInfoService")]
public class UserInfoService : IUserInfoService
{
/// <summary>
/// 获取用户信息
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
[Description("UserInfoService.GetUserInfo")]
public virtual UserInfo GetUserInfo([Description("UserInfoService.GetUserInfo name")] string name)
{
return new UserInfo { Name = name };
}
}
using DescriptionAttribute = System.ComponentModel.DescriptionAttribute;
[TestFixture]
public class AttributeTests
{
/// <summary>
/// Reference to the Castle Windsor Container.
/// </summary>
public IWindsorContainer IocContainer { get; private set; }
[SetUp]
public void Initialize()
{
IocContainer = new WindsorContainer();
IocContainer.Kernel.ProxyFactory = new CustomProxyFactory();
IocContainer.Register(
Component.For<UserInfoService>()
.Proxy
.AdditionalInterfaces(typeof(IUserInfoService))
.LifestyleTransient()
);
}
/// <summary>
///
/// </summary>
[Test]
public void GetAttributeTest()
{
var userInfoService = IocContainer.Resolve<UserInfoService>();
Assert.IsNotNull(userInfoService);
var type = userInfoService.GetType();
Assert.IsTrue(type != typeof(UserInfoService));
var attribute = type.GetCustomAttribute<DescriptionAttribute>();
Assert.IsTrue(attribute != null);
Trace.WriteLine(attribute.Description);
var method = type.GetMethod("GetUserInfo");
attribute = method.GetCustomAttribute<DescriptionAttribute>();
Assert.IsTrue(attribute != null);
Trace.WriteLine(attribute.Description);
var parameter = method.GetParameters().First();
attribute = parameter.GetCustomAttribute<DescriptionAttribute>();
Assert.IsTrue(attribute != null);
Trace.WriteLine(attribute.Description);
}
}
#hikalkan I faced the same problem and I was looking for a solution as well. The best I could encounter was
How to add an attribute to a property at runtime
Proxy the controller (in my case a dynamic controller) with a new wrapper that set those attributes in the class itself and its methods..
I've got an layered design in my webapplication with an generic service and repository construction. I want to use code first so I can code my entities and then create/update my database. However I can't seem to get it working. I'm new to the code first concept with generating the database and seeding it, so it could very well be something obvious;-)
My application design is as following.
Website
Website.DAL
Website.TESTS (not used yet)
The website.DAL project contains my generic service and repository, as well as DataContext and my entities. The idea is that I can instantiate an generics ervice inside my controller of an certain entity. The service can contain more functions to do calculations etc.. and the repository is only intended to handle the CRUD-actions. The website project ofcourse has an reference to the Website.DAL project and also EF5 is installed in both project through NuGet.
The DataContext looks like this:
using System.Data.Entity;
using System.Web;
using Website.DAL.Entities;
namespace Website.DAL.Model
{
public class MyContext : DbContext
{
public IDbSet<Project> Projects { get; set; }
public IDbSet<Portfolio> Portfolios { get; set; }
/// <summary>
/// The constructor, we provide the connectionstring to be used to it's base class.
/// </summary>
public MyContext()
: base("MyConnectionstringName")
{
//MyContext.Database.Initialize(true);
//if (HttpContext.Current.IsDebuggingEnabled)
//{
// //Database.SetInitializer<MyContext>(new DatabaseInitializer());
// Database.SetInitializer<MyContext>(null);
//}
//else
//{
// //Database.SetInitializer<MyContext>(new CreateInitializer());
//}
}
static MyContext()
{
Database.SetInitializer<MyContext>(null);
}
/// <summary>
/// This method prevents the plurarization of table names
/// </summary>
/// <param name="modelBuilder"></param>
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<System.Data.Entity.ModelConfiguration.Conventions.PluralizingTableNameConvention>();
}
//public void Seed(MyContextContext)
//{
// // Normal seeding goes here
// Context.SaveChanges();
//}
}
}
I've also created an DatabaseInitialiser class which is currently empty, but the idea ofcourse is to make it seed my database when it's created or updated.
The DatabaseInitialiser class looks like this:
using System.Data.Entity;
using Website.DAL.Model;
namespace Website.DAL
{
public class DatabaseInitializer : DropCreateDatabaseIfModelChanges<MyContext>
{
public DatabaseInitializer()
{
}
protected override void Seed(MyContextcontext)
{
//TODO: Implement code to seed database
//Save all changes
context.SaveChanges();
}
}
}
Since the GenericService isn't relevant to the question i'll leave it out since it's currenlty only making direct calls to the repository without any specific business intelligence.
The generic repository used looks like this. Things still need to be improved here, but it works for now.
GenericRepository
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using Website.DAL.Model;
using Website.DAL.RepositoryInterfaces;
namespace Website.DAL.Repositories
{
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
{
#region Implementation of IRepository<TEntity>
//protected SceObjectContext DataContext;
//protected ObjectContext DataContext;
private MyContext _context;
//private IObjectSet<T> ObjectSet;
private DbSet<TEntity> _dbSet;
public GenericRepository()
{
//DataContext = SceObjectContext.Current;
//DataContext = new ObjectContext("dbConnection");
_context = new MyContext();
//ObjectSet = DataContext.CreateObjectSet<T>();
_dbSet = _context.Set<TEntity>();
}
/// <summary>
/// Inserts a new object into the database
/// </summary>
/// <param name="entity">The entity to insert</param>
public void Insert(TEntity entity)
{
//var entitySetName = GetEntitySetName(typeof(T));
//DataContext.AddObject(entitySetName, entity);
_dbSet.Add(entity);
}
/// <summary>
/// Deletes the specified entity from the database
/// </summary>
/// <param name="entity">The object to delete</param>
public void Delete(TEntity entity)
{
//DataContext.DeleteObject(entity);
if (_context.Entry(entity).State == System.Data.EntityState.Detached)
{
_dbSet.Attach(entity);
}
_dbSet.Remove(entity);
}
/// <summary>
/// Saves all pending chances to the database
/// </summary>
public void Save()
{
_context.SaveChanges();
}
/// <summary>
/// Retrieves the first object matching the specified query.
/// </summary>
/// <param name="where">The where condition to use</param>
/// <returns>The first matching object, null of none found</returns>
public TEntity First(Expression<Func<TEntity, bool>> #where)
{
return _dbSet.FirstOrDefault(where);
}
/// <summary>
/// Gets a list of all objects
/// </summary>
/// <returns>An strong typed list of objects</returns>
public IEnumerable<TEntity> GetAll()
{
return _dbSet.AsEnumerable<TEntity>();
}
/// <summary>
/// Returns ans iQueryable of the matching type
/// </summary>
/// <returns>iQueryable</returns>
public IQueryable<TEntity> AsQueryable()
{
return _dbSet.AsQueryable();
}
#endregion
}
}
I've got two entities which i've created. Portfolio is one of them which is displayed belowd. Project is the second one which just is an simple POCO class with an Id and a few properties.
Portfolio.cs
public class Portfolio
{
[Key]
public Guid Id { get; set; }
public String Name { get; set; }
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
public bool IsPublished { get; set; }
public virtual ICollection<Project> Projects { get; set; }
}
All the classes above are maintained in my Website.DAL project. The Global.asax in my Website project contains some code calling the initialiser which, as far as I know, should make sure the seeding can be done in the near future and maintain the database table.
Global.asax
try
{
//Regenerate database if needed.
//Database.SetInitializer<MyContext>(new DropCreateDatabaseIfModelChanges<MyContext>());
//Database.SetInitializer(new DatabaseInitializer());
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<BorloContext>());
//Database.SetInitializer<MyContext>(new MigrateDatabaseToLatestVersion<MyContext>());
}
catch (Exception)
{
throw;
}
Just for the sake of it i've got a piece of code in my HomeController which should get an id of all portfolio items.
var list = _portfolioService.GetAll();
The following things are happening when debugging through the code;
Initialiser code in Global.asax passes.
Constructor for databaseinitialiser is called
Code in homecontroller doesn't throw an exception. But when adding an watch to the call to '_portfolioService.GetAll();' i'm getting the following exception;
I can't figure out what is going wrong here. Ofcourse the exception isn't good but I can't view the inner exception since it isn't giving me one. What could I possibly do to get this working? Or isn't the thing I want to achieve not possible and should the DAL-layer be merged into the website to get the code generation working?
UPDATE 1:
Okay, i've changed the following line in my context
Database.SetInitializer<MyContext>(null);
To
Database.SetInitializer<MyContext>(new DatabaseInitializer());
Now i'm getting this error and stacktrace when debugging the '_portfolioService.GetAll();' call in the homecontroller
Error:
Model compatibility cannot be checked because the database does not
contain model metadata. Model compatibility can only be checked for
databases created using Code First or Code First Migrations.
bij System.Data.Entity.Internal.ModelCompatibilityChecker.CompatibleWithModel(InternalContext internalContext, ModelHashCalculator modelHashCalculator, Boolean throwIfNoMetadata)
bij System.Data.Entity.Internal.InternalContext.CompatibleWithModel(Boolean throwIfNoMetadata)
bij System.Data.Entity.Database.CompatibleWithModel(Boolean throwIfNoMetadata)
bij System.Data.Entity.DropCreateDatabaseIfModelChanges`1.InitializeDatabase(TContext context)
bij System.Data.Entity.Database.<>c__DisplayClass2`1.<SetInitializerInternal>b__0(DbContext c)
bij System.Data.Entity.Internal.InternalContext.<>c__DisplayClass8.<PerformDatabaseInitialization>b__6()
bij System.Data.Entity.Internal.InternalContext.PerformInitializationAction(Action action)
bij System.Data.Entity.Internal.InternalContext.PerformDatabaseInitialization()
bij System.Data.Entity.Internal.LazyInternalContext.<InitializeDatabase>b__4(InternalContext c)
bij System.Data.Entity.Internal.RetryAction`1.PerformAction(TInput input)
bij System.Data.Entity.Internal.LazyInternalContext.InitializeDatabaseAction(Action`1 action)
bij System.Data.Entity.Internal.LazyInternalContext.InitializeDatabase()
bij System.Data.Entity.Internal.InternalContext.Initialize()
bij System.Data.Entity.Internal.InternalContext.GetEntitySetAndBaseTypeForType(Type entityType)
bij System.Data.Entity.Internal.Linq.InternalSet`1.Initialize()
bij System.Data.Entity.Internal.Linq.InternalSet`1.GetEnumerator()
bij System.Data.Entity.Infrastructure.DbQuery`1.System.Collections.Generic.IEnumerable<TResult>.GetEnumerator()
bij System.Linq.SystemCore_EnumerableDebugView`1.get_Items()
Since no other solution came by I decided to change my approach.
I've first created the database myself and made sure the correct SQL user was configured and I had access.
Then I removed the initializer and the code from the Global.asax file. After that I ran the following command in the Package Manager Console (since the layered design I had to select the correct project in the console);
Enable-Migrations
After the migrations where enabled and I made some last minute changes to my entities I ran the command below to scaffold an new migration;
Add-Migration AddSortOrder
After my migrations were created I ran the following command in the console and voila, the database was updated with my entities;
Update-Database -Verbose
To be able to seed the database when running the migration i've overridden the Seed method in my Configuraton.cs class, which was created when enabling the migrations. The final code in this method is like this;
protected override void Seed(MyContext context)
{
// This method will be called after migrating to the latest version.
//Add menu items and pages
if (!context.Menu.Any() && !context.Page.Any())
{
context.Menu.AddOrUpdate(new Menu()
{
Id = Guid.NewGuid(),
Name = "MainMenu",
Description = "Some menu",
IsDeleted = false,
IsPublished = true,
PublishStart = DateTime.Now,
LastModified = DateTime.Now,
PublishEnd = null,
MenuItems = new List<MenuItem>()
{
new MenuItem()
{
Id = Guid.NewGuid(),
IsDeleted = false,
IsPublished = true,
PublishStart = DateTime.Now,
LastModified = DateTime.Now,
PublishEnd = null,
Name = "Some menuitem",
Page = new Page()
{
Id = Guid.NewGuid(),
ActionName = "Some Action",
ControllerName = "SomeController",
IsPublished = true,
IsDeleted = false,
PublishStart = DateTime.Now,
LastModified = DateTime.Now,
PublishEnd = null,
Title = "Some Page"
}
},
new MenuItem()
{
Id = Guid.NewGuid(),
IsDeleted = false,
IsPublished = true,
PublishStart = DateTime.Now,
LastModified = DateTime.Now,
PublishEnd = null,
Name = "Some MenuItem",
Page = new Page()
{
Id = Guid.NewGuid(),
ActionName = "Some Action",
ControllerName = "SomeController",
IsPublished = true,
IsDeleted = false,
PublishStart = DateTime.Now,
LastModified = DateTime.Now,
PublishEnd = null,
Title = "Some Page"
}
}
}
});
}
if (!context.ComponentType.Any())
{
context.ComponentType.AddOrUpdate(new ComponentType()
{
Id = Guid.NewGuid(),
IsDeleted = false,
IsPublished = true,
LastModified = DateTime.Now,
Name = "MyComponent",
PublishEnd = null,
PublishStart = DateTime.Now
});
}
try
{
// Your code...
// Could also be before try if you know the exception occurs in SaveChanges
context.SaveChanges();
}
catch (DbEntityValidationException e)
{
//foreach (var eve in e.EntityValidationErrors)
//{
// Console.WriteLine("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:",
// eve.Entry.Entity.GetType().Name, eve.Entry.State);
// foreach (var ve in eve.ValidationErrors)
// {
// Console.WriteLine("- Property: \"{0}\", Error: \"{1}\"",
// ve.PropertyName, ve.ErrorMessage);
// }
//}
//throw;
var outputLines = new List<string>();
foreach (var eve in e.EntityValidationErrors)
{
outputLines.Add(string.Format(
"{0}: Entity of type \"{1}\" in state \"{2}\" has the following validation errors:",
DateTime.Now, eve.Entry.Entity.GetType().Name, eve.Entry.State));
foreach (var ve in eve.ValidationErrors)
{
outputLines.Add(string.Format(
"- Property: \"{0}\", Error: \"{1}\"",
ve.PropertyName, ve.ErrorMessage));
}
}
System.IO.File.AppendAllLines(#"c:\temp\errors.txt", outputLines);
throw;
}
}
The disadvantage at the moment is that I have to manually migrate with (only) 2 commands in the package manager console. But the same time, the fact that this doesn't happen dynamically is also good because this prevents possibly inwanted changes to my database. Further everything works just perfect.
I'm trying to make a wizard in MVC3 using Entity Framework. It needs to keep the state of an object (an article in this case) across a couple of steps.
I have a static variable in my controller that instantiates a new Article. In the different Actions I use TryUpdateModel to map the form to the static variable. The problem is, it seems that TryUpdateModel() updates the database as well. I need TryUpdateModel to do the automatic mapping, and update the static _article variable, but I don't want it to persist to the database until the last step!
N.B: I know there are a lot of possible solutions for creating a wizard in MVC, but I'd like to know what to do to make this way work, so please no alternatives for an MVC wizard-pattern.
Thanks.
namespace website.Controllers
{
public class ArticlesController : BaseController
{
// private static variable to hold the chosen article in the wizard
private static articles _article = new articles();
/// <summary>
/// Index page shows a list of articles in a webgrid
/// </summary>
/// <returns></returns>
public ActionResult Index()
{
List<articles> _articles = Data.getArticles();
return View(_articles);
}
/// <summary>
/// First page of the article wizard
/// </summary>
/// <returns></returns>
public ActionResult BasicDetails(string id, string nextButton)
{
// back or next doesn't matter - store form values
if (_article != null) TryUpdateModel(_article);
if (nextButton != null)
{
return RedirectToAction("ArticleGroup");
}
else
{
_article = Data.GetArticleById(id);
return View(_article);
}
}
/// <summary>
/// Second page of the article wizard
/// </summary>
/// <returns></returns>
public ActionResult ArticleGroup(string nextButton, string backButton)
{
TryUpdateModel(_article);
if (backButton != null)
return RedirectToAction("BasicDetails");
else if (nextButton != null)
{
return RedirectToAction("Price");
}
else
{
return View(_article);
}
}
/// <summary>
/// Third page of the article wizard
/// </summary>
/// <returns></returns>
public ActionResult Price(string nextButton, string backButton)
{
TryUpdateModel(_article);
if (backButton != null)
{
return RedirectToAction("ArticleGroup");
}
else if (nextButton != null)
return RedirectToAction("LinkedClubs");
else
{
return View(_article);
}
}
/// <summary>
/// Last page of the article wizard
/// </summary>
/// <returns></returns>
public ActionResult LinkedClubs(string backButton)
{
if (backButton != null)
return RedirectToAction("Price");
else
return View(_article);
}
}
}
Rather than using a static variable to hold your state information (that is a critical error btw) you should pass a state bag holding the information that you need in between pages.
Usually data entities (entities mapped to database) and viewmodel entities (entities with that user works) used separately. When user posted data after some step - you make TryUpdateModel() to session object (specific for user, not for all application as static variable).
At last step you call business logic method UpdateModel(viewmodel), that update all columns by id of viewmodel, using all filled properties.
I need to call something in my application start to start my quartz.net scheduler.
The problem is that I need to pass in a repository into my service layer that normally is done with ninject and dependency injection.
//global.aspx
public class MvcApplication : System.Web.HttpApplication
{
private readonly IScheduledRemindersService scheduledRemindersService;
public MvcApplication(IScheduledRemindersService
scheduledRemindersService)
{
this.scheduledRemindersService = scheduledRemindersService;
}
protected void Application_Start()
{
//other default stuff here like mvc routers.
scheduledRemindersService.RemindersSchedule();
}
}
private readonly IReminderRepo reminderRepo;
public ScheduledRemindersService(IReminderRepo reminderRepo)
{
this.reminderRepo = reminderRepo;
}
private readonly IReminderRepo reminderRepo;
public ScheduledRemindersService(IReminderRepo reminderRepo)
{
this.reminderRepo = reminderRepo;
}
I have NHibernate set to so when It seems IReminderRepo it shoudl bind it and in IReminderRepo I have
private readonly ISession session;
public ReminderRepo(ISession session)
{
this.session = session;
}
This will also get automatically binded through nhibernate.
This won't work though as the global.aspx only allows no argument constructors.
So how can inject the right classes to these interfaces? Especially the nhibernate session that is the most important thing I need.
Edit
public class NhibernateSessionFactoryProvider : Provider<ISessionFactory>
{
protected override ISessionFactory CreateInstance(IContext context)
{
var sessionFactory = new NhibernateSessionFactory();
return sessionFactory.GetSessionFactory();
}
}
public class NhibernateModule : NinjectModule
{
public override void Load()
{
Bind<ISessionFactory>().ToProvider<NhibernateSessionFactoryProvider>().InSingletonScope();
Bind<ISession>().ToMethod(context => context.Kernel.Get<ISessionFactory>().OpenSession()).InRequestScope();
}
}
// in the global.aspx
protected IKernel CreateKernel()
{
var modules = new INinjectModule[]
{
new NhibernateModule(),
new ServiceModule(),
new RepoModule(),
};
return new StandardKernel(modules);
}
// in RepoModule()
Bind<IReminderRepo>().To<ReminderRepo>();
// in serviceModule
Bind<IScheduledRemindersService>().To<ScheduledRemindersService>()
We use a method like this in our Global.asax.cs file for exactly this purpose (it gets called from Application_Start:
private static void SetupScheduling(IKernel kernel)
{
var scheduler = SchedulerUtil.Scheduler;
scheduler.JobFactory = kernel.Get<IJobFactory>();
scheduler.Start();
}
And we have IJobFactory bound to the following class:
public class QuartzJobFactory : IJobFactory
{
private readonly IObjectFactory<IJob> _jobFactory;
private readonly LogService _logService;
public QuartzJobFactory(IObjectFactory<IJob> jobFactory,
LogService logService)
{
_jobFactory = jobFactory;
_logService = logService;
}
/// <summary>
/// Called by the scheduler at the time of the trigger firing, in order to
/// produce a <see cref="T:Quartz.IJob"/> instance on which to call Execute.
/// </summary>
/// <remarks>
/// <p>It should be extremely rare for this method to throw an exception -
/// basically only the the case where there is no way at all to instantiate
/// and prepare the Job for execution. When the exception is thrown, the
/// Scheduler will move all triggers associated with the Job into the
/// <see cref="F:Quartz.TriggerState.Error"/> state, which will require human
/// intervention (e.g. an application restart after fixing whatever
/// configuration problem led to the issue wih instantiating the Job.
/// </p>
/// </remarks>
/// <param name="bundle">The TriggerFiredBundle from which the <see cref="T:Quartz.JobDetail"/>
/// and other info relating to the trigger firing can be obtained.
/// </param><throws>SchedulerException if there is a problem instantiating the Job. </throws>
/// <returns>
/// the newly instantiated Job
/// </returns>
public IJob NewJob(TriggerFiredBundle bundle)
{
Type jobType;
try
{
Require.ThatArgument(bundle != null);
Require.ThatArgument(bundle.JobDetail != null);
jobType = bundle.JobDetail.JobType;
}
catch (Exception e)
{
// This shouldn't ever happen, but if it does I want to know about it.
_logService.LogCritical(() => e.ToString());
throw;
}
try
{
return _jobFactory.GetObject(jobType);
}
catch (Exception e)
{
_logService.LogCritical(() => "An exception was thrown while creating job of type {0}: {1}"
.With(jobType, e));
throw;
}
}
}
And IObjectFactory is a simple interface that just abstracts away the Kernel so we're not depending on Ninject everywhere:
/// <summary>
/// Similar to IFactory, but this factory produces types based on a dynamic
/// <see cref="Type"/> object. If the given Type object is not of the given
/// "T" type, an exception will be thrown.
/// </summary>
/// <typeparam name="T">A parent-level type representing what sort of values
/// are expected. If the type could be anything, try using <see cref="object"/>
/// </typeparam>
public interface IObjectFactory<out T>
{
T GetObject(Type type);
}
IObjectFactory is then bound to a class like this:
/// <summary>
/// This implementation of the generic <see cref="IFactory{T}"/> and
/// <see cref="IObjectFactory{T}"/> classes uses Ninject to supply instances of
/// the given type. It should not be used explicitly, but will rather be used
/// by the DI framework itself, to provide instances to services that depend on
/// IFactory objects.
/// This implementation takes the injection context as a constructor argument, so that
/// it can reuse elements of the context when it is asked to supply an instance
/// of a type.
/// In order for this to work, you will need to define bindings from <see cref="IFactory{T}"/>
/// and <see cref="IObjectFactory{T}"/> to this class, as well as a binding from
/// <see cref="IContext"/> to a method or factory that returns the current binding
/// context.
/// </summary>
/// <typeparam name="T">The Type of the service to be produced by the Get method.</typeparam>
public class InjectionFactory<T> : IFactory<T>, IObjectFactory<T>
{
private readonly IKernel _kernel;
private readonly IParameter[] _contextParameters;
/// <summary>
/// Constructs an InjectionFactory
/// </summary>
/// <param name="injectionContext">The context in which this injection is taking place.</param>
public InjectionFactory(IContext injectionContext)
{
_contextParameters = injectionContext.Parameters
.Where(p => p.ShouldInherit).ToArray();
_kernel = injectionContext.Kernel;
}
public T Get()
{
try
{
return _kernel.Get<T>(_contextParameters.ToArray());
}
catch (Exception e)
{
throw e.Wrap(() => "An error occurred while attempting to instantiate an object of type <{0}>".With(typeof(T)));
}
}
public T GetObject(Type type)
{
if (type == null)
{
throw new ArgumentNullException("type");
}
if (!typeof (T).IsAssignableFrom(type))
{
throw new InvalidCastException(type.FullName + " is not a child type of " + typeof (T).FullName);
}
try
{
return (T)_kernel.Get(type, _contextParameters);
}
catch (Exception e)
{
throw e.Wrap(() => "An error occurred while attempting to instantiate an object of type <{0}>".With(typeof(T)));
}
}
}
... using a binding that looks like this:
Bind(typeof (IObjectFactory<>)).To(typeof (InjectionFactory<>));
// Injection factories ask for the injection context.
Bind(typeof (IContext)).ToMethod(c => c.Request.ParentContext);
So the overall effect is that we use the Ninject kernel to create the IJobFactory, which uses constructor injection to take an IObjectFactory<IJob>, which is invoked to produce any IJobs that Quartz requires. Those job classes can therefore use constructor-based injection, and Ninject is indirectly instantiating them.
i don't think you have understood IoC very well. When you say the ISession will get auto binded through NHibernate - what are you thinking happens? NHibernate won't manage that for you. Session lifestyle management is your domain.
I don't think there is enough code to really help you. the service as well as the repository would need to be managed by your IoC to be able to inject one into the other - unless you have a direct reference to the IoC - which is not recommended.
You should get the instance of IScheduledRemindersService from your IoC container. The container should take charge of injecting all of the dependencies for you as it creates/resolves the instance.
I'm not familiar with NInject so I can't tell you how it should be done, but you should do something along the lines of:
protected void Application_Start()
{
// 1- register IoC container
// 2- resolve instance of IScheduledRemindersService
// 3- invoke RemindersService() method on instance
}