0

I'm new to Castle Windsor/Fluent NHibernate / NHibernate and am trying to work with all of them in a .NET MVC3 project as a learning exercise.

Went through this excellent tutorial to get going, and trying to stay away from repositories, ended up with the following classes/mappings:

 // Entities
public abstract class EntityBase
{
    public virtual int Id { get; private set; }
    public virtual DateTime Modified { get; set; }
}

public class Section : EntityBase
{
    public virtual String Name { get; set; }
    public virtual int Sortorder { get; set; }
    public virtual IList<ContentPage> Pages { get; set; }

    public Section()
    {
        Pages = new List<ContentPage>();
    }

    public virtual void AddContentPage(ContentPage contentPage)
    {
        contentPage.Section = this;
        this.Pages.Add(contentPage);
    }
}

public class ContentPage : EntityBase
{
    public virtual String Title { get; set; }
    public virtual int Sortorder { get; set; }
    public virtual String MetaKeywords { get; set; }
    public virtual String MetaDescription { get; set; }
    public virtual String Slug { get; set; }

    public virtual Section Section { get; set; }
}

//Mappings
public abstract class EntityBaseMap<T> : ClassMap<T> where T : EntityBase
{
    public EntityBaseMap()
    {
        Id(x => x.Id);
        Map(x => x.Modified);
    }
}

public class SectionMap : EntityBaseMap<Section>
{
    public SectionMap()
    {
        Map(x => x.Name);
        Map(x => x.Sortorder);
        HasMany(x => x.Pages)
            .Inverse()  
            .Cascade.All();
    }
}
public class ContentPageMap : EntityBaseMap<ContentPage>
{
    public ContentPageMap()
    {
        Map(x => x.Title);
        Map(x => x.Sortorder);
        Map(x => x.MetaKeywords);
        Map(x => x.MetaDescription);
        Map(x => x.Slug);
        References(x => x.Section);

    }
}

// SectionsController

    private readonly ISession session;

    public ActionResult Edit(int id)
    {
        Section section = session.QueryOver<Section>().Where(x => x.Id == id).SingleOrDefault();
        if (section == null)
        {
            return HttpNotFound();
        }
        return View(section);
    }

    [HttpPost]
    public ActionResult Edit(Section section)
    {
        section.Modified = DateTime.Now;
        if (ModelState.IsValid)
        {
            session.Update(section); 
            return RedirectToAction("Index");
        }
        return View();
    }

The problem I'm running into is when I edit a 'Section', the form displays fine and the hidden 'id' has the correct value. However, when that form is submitted, the value of the id column inside the 'Edit' action is 0. What's interesting is the 'Modified' is also part of the EntityBase class, but that's populated fine.

Needless to say, adding a new 'Section' is not a problem as the id is generated correctly by the database.

So I know I definitely missed something somewhere, and I'm just not seeing it. Can anyone please shed any light on what I'm missing?

Edit: Thanks to @Linkgoron's answer below, added a ViewModel...

public class SectionViewModel
{
    public int Id { get; set; }

    [Required(ErrorMessage = "Section Name is required")]
    [StringLength(25, ErrorMessage = "Name must be less than 25 characters")]
    public String Name { get; set; }
    [Required]
    public int Sortorder { get; set; }
}

// Updated Controller methods   
public ActionResult Edit(int id)
    {
        Section section = session.Load<Section>(id);
        if (section == null)
        {
            return HttpNotFound();
        }
        return View(section);
    }

    [HttpPost]
    public ActionResult Edit(SectionViewModel sectionInputModel)
    {
        var section = session.Get<Section>(sectionInputModel.Id);
        section.Modified = DateTime.Now;
        if (ModelState.IsValid)
        {
            Mapper.CreateMap<SectionViewModel, Section>();
            Mapper.Map(sectionInputModel, section);
            session.SaveOrUpdate(section); 
            return RedirectToAction("Index");
        }
        return View();
    }

Now I do get the correct id, and it is mapped over correctly as well, but SaveOrUpdate doesn't seem to modify the data in the database. What else did I miss?

Edit 2: Doh!

Needed to Flush i.e.

session.SaveOrUpdate(section);
session.Flush();
return RedirectToAction("Index");

Thanks.

seekay
  • 2,493
  • 3
  • 27
  • 33
  • You shouldn't use Mapper.CreateMap() every time, you only have to execute CreateMap once, do it in the application_start. It is a heavy operation that uses reflection. Also, when using nhibernate you should always execute stuff inside a transaction http://nhprof.com/Learn/Alerts/DoNotUseImplicitTransactions – Linkgoron Apr 23 '11 at 07:28
  • Yep - figured as much. The above was just so that I got it working. Thanks a lot for the pointers! – seekay Apr 23 '11 at 17:01

1 Answers1

1

I Believe that this is because Id is set as private, so the Model Binder can't set the value.

The best way to handle this would be to create a ViewModel. The basics of a ViewModel is creating a smaller model per view and mapping between the the domain model and the view model.

Basically, when you're not using ViewModels you're open over-posting/under-posting (Brad Wilson's blog) which is a BIG security problem. There also other benefits such as better refactoring, better validation and better seperation of concerns.

More info:

ViewModel Best Practices

Using view models in ASP.NET MVC 3

You've also got some unrelated stuff going there like that Modified property. nHibernate has built-in support for this kind of property using the Version mapping (Ayende's blog , fluent nhibernate)

Also, it's preferred using the Get/Load methods of the Session for loading objects by id instead of querying for the object (Ayende's blog).

Community
  • 1
  • 1
Linkgoron
  • 4,866
  • 2
  • 26
  • 26
  • Thanks for the info on [a] ViewModels [b] the Get/Load methods, and [c] the Version property - super helpful! Still having a problem with saving & the version as a timestamp, but will research the latter some more – seekay Apr 23 '11 at 03:29