开发者

Saving entities with foreign key in ASP.NET MVC

I need some help doing something I am assuming is simple. I am using ASP.net MVC 3 with CodeFirst (CTP5)

I have two entities: Company and Location. A Company can many Locations. The classes are as follows (stripped of all needless info)

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

    [Required]
    public string Name { get; set; }

    public virtual ICollection<Location> Locations { get; set; }
}

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

    [Required]
    public string Name { get; set; }

    [Required]
    public virtual Company Company { get; set; }

}

Now in my controller, I only allow creating a Location within the context of a Company, so the Company Id is always passed in (In the View, I show the name of the Company in a read only field, but do not allow the user to change/edit that.

    public ActionResult Create(int companyId)
    {
        Company company = _session.Single<Company>(c => c.Id == companyId);
        Location newLocation = new Location {Company = company};
        return View(newLocation);
    } 

    [HttpPost]
    public ActionResult Create(Location location)
    {
        if (ModelState.IsValid)
        {
            _session.Add<Location>(location);
            _session.CommitChanges();                
            return RedirectToAction("Index");
        } else {
            return View(location);
        }
    }

Now whenever I try to create a new location, the ModelState.IsValid is always false due to the fact that location.Company.Name is not supplied and is a required field on Company. I am never trying to create a new company here, I merely trying to create a location with a reference to the correct company. I don't want to add the Name property to the view just to get the ModelState to validate. How can this be accomplished easily? Should I be passing something different 开发者_如何学Cfrom the view? or to the view?


As i was explaining in my comment, i stumbled upon a small change which makes this work, still think it is a kludge as I dont see why i need to duplicate the child Id paramater, but the best/simplest one I could find that should be easy to update in the future and I will be the first to admit I dont know all (or most) of what there is in CodeFirst.

I changed the location class from:

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

    [Required]      
    public string Name { get; set; }        

    [Required]      
    public virtual Company Company { get; set; }    
}

To:

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

    [Required]      
    public string Name { get; set; }        

    [Required]      
    public int CompanyId { get; set; }
    public virtual Company Company { get; set; }    
}

Now as far as I can tell, this hasnt changed the database schema at all, but now the only thing which is required on the Location (when validating) is that there is an ID in the CompanyId field, the Company property can be left null (before, it was trying to validate a proper Company since the Company class was required).

So now when creating a Location, the only thing the view needs to pass back in the CompanyId.

Anyone see any drawbacks? or a better way?


You can easily just add a hidden field with company name on your form. Its data will get picked up and sent to your POST action as expected.

If you don't use company name on the server side at all, you can fill this hidden field up with any dummy text as long as company ID is correct (because you use it). But if you do need correct company name with your ID, you'll have to fill actual name into hidden field instead of some dummy text.

Anyway. This will add a hidden field for your company name

<%= Html.HiddenFor(m => m.Company.Name) %>

and if you don't have your Company property set to anything then, you can as well use this:

<%= Html.Hidden("Company.Name", "Super duper company") %>

Whether this value is an actual name or a dummy name it doesn't really matter. Your model will be valid upon postback.

Regular postback or Ajax?

I suppose you're using a regular postback and not an Ajax postback. If you do use Ajax one and you're using jQuery and calling $.post() or $.ajax() or something similar, you can always provide whatever object you like. You can fill any value for the company name in your JavaScript code.

If you have complex objects in JavaScript and would like to send them to your controller action with strong type parameters (so they will be validated) you can use this little jQuery plugin of mine, that prepares any JSON object to be correctly sent to Asp.net MVC action so data will be automatically data bound to your strong types to get validated. Does JavaScript dates as well. :)

Additional information

Since you pointed out (in a comment) that your entities are very much work in progress and you may be adding/removing required fields, this may be very tedious to also change all related views.

Yet there are ways you may take, to make your life a bit easier when it comes to changable models and different validation scenarios depending on object state:

  1. Have separate view models for creation process that also include functionality to auto-convert to application model class:

    public class LocationCreate
    {
        public int Id { get; set; }
    
        [Required]
        public string Name { get; set; }
    
        [Required]
        public int CompanyId { get; set; }
    
        public Location ToModelInstance()
        {
            return new Location {
                Id = this.Id,
                Name = this.Name,
                Company = new Company {
                    Id = this.CompanyId,
                    Name = "Super duper company Ltd." // you can as well omit this line
                }
            };
        }
    }
    
  2. Use inheritance:

    public class CompanyBase
    {
        public int Id { get; set; }
    }
    
    public class Company : CompanyBase
    {
        [Required]
        public string Name { get; set; }
    
        public virtual ICollection<Location> Locations { get; set; }
    }
    
    public class Location
    {
        public int Id { get; set; }
    
        [Required]
        public string Name { get; set; }
    
        [Required]
        public virtual CompanyBase Company { get; set; }
    
    }
    
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜