MVC Controller using related data - asp.net-mvc

Hope someone can help me with this.
I have a controller in my ASP.NET MVC project that is used for editing a so called survey form. The survey form has a 1-to-1 relation with objects of type Form. When saving the survey form I also want to set the name of the form. However the form property is null. The FormID property has the correct value (using database first, EF5). Here is the problem.
[HttpPost]
public ActionResult Edit(SurveyForm surveyform)
{
if (ModelState.IsValid)
{
db.Entry(surveyform).State = EntityState.Modified;
// This cannot be done because surveyform.Form is null
surveyform.Form.Name = "Wish this would work!";
db.SaveChanges();
}
}
My question is: how can I 'attach' surveyform to the model so that is loads related data?
Thanks in advance!

At a guess, I'd say the entity being POSTed back isn't being tracked, so Entity Framework doesn't realise it's from the database. The line db.Entry(surveyForm) gets the entry matching the POSTed form, but you're not retaining it. Try this:
[HttpPost]
public ActionResult Edit(SurveyForm surveyform)
{
if (ModelState.IsValid)
{
var formEntry = db.Entry(surveyform);
formEntry.State = EntityState.Modified;
formEntry.Form.Name = "This might work...";
db.SaveChanges();
}
}
Hopefully, the .Entry() will get you the database's version. Unfortunately, you'll probably have to copy the property values accross, although you might find you can use .Attach() and then copy over the navigation properties from the database's version.
Having said that, it's generally a good idea to not use your database models in the view if you can help it; separation of concerns and all that. If there's more than a couple of properties on that model that are needed for the database but not the view (or vice versa) then you might want to use a local view model for it and just copy the properties to a freshly-retrieved database entity.

Try:
db.Entry(surveyform).Reference(x => x.Form).Load();
surveyform.Form = db.Entry(surveyform).Reference(x => x.Form).CurrentValue;

In your model, make sure you have a navigation propery like this:
public virtual Form Form { get; set; }
And then in your DbContext:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<SurveyForm>().HasOptional(p => p.Form);
}
HasOption can be replaced with HasRequired.
I have never had the exact issue you are having, so I hope this helps.

Eventually I combined multiple suggestions above and ended up with the following.
I added the following to my view:
#Html.TextBox("formName", Model.Form.Name)
and then changed the code in my controller to:
[HttpPost]
public ActionResult Edit(string formName, SurveyForm surveyForm)
{
if (ModelState.IsValid)
{
var surveyFormEntry = db.Entry<SurveyForm>(surveyForm);
db.SurveyForms.Attach(surveyForm);
surveyFormEntry.State = EntityState.Modified;
surveyFormEntry.Reference(x => x.Form).Load();
surveyForm.Form = db.Entry(surveyForm).Reference(x => x.Form).CurrentValue;
surveyForm.Form.Name = formName;
db.SaveChanges();
return RedirectToAction("Index", new { surveyId = surveyForm.SurveyID });
}
It was a lot of trial and error. A way of programming I so disapprove on and dislike but I'm happy it finally works.
Thanks all!

Related

Multiple views modifying one object MVC

I am building a service which requires a somewhat lengthy setup process. I have it broken into 4 models and 4 corresponding views. They are Setup, Setup2, Setup3, and Setup4. Each of these views gathers information from the user which is stored in a User object. I have been passing the user along like this:
[HttpPost]
public ActionResult Setup(FormCollection values)
{
User registeringUser = new User();
registeringUser.email = User.Identity.Name;
registeringUser.fName = values["fName"];
registeringUser.lName = values["lName"];
registeringUser.phone = values["phone"];
return RedirectToAction("/Setup2", registeringUser);
}
For some reason, this seems to work just fine for the first jump (from Setup to Setup2) but after that I'm getting weird behavior, such as User. getting set to null when the User is passed to another View.
In a related, but slightly different issue, I need the last screen (Setup4) to be recursive. This screen adds a course in which the user is enrolled, and if they don't check the "This was my last class" button, it needs to basically clear the form so they can enter another course.
The entire Controller looks like this:
[HttpPost]
public ActionResult Setup4(FormCollection values, User registeringUser)
{
// values["allClassesAdded"] returns "false" as a string if box is unchecked, returns "true,false" if checked.
// Solution: parse string for "true"
if (utils.parseForTrue(values["allClassesAdded"]))
{
// TODO Redirect to "congratulations you're done" page.
database.CreateUserInDB(registeringUser);
return Redirect("/Home");
}
else
{
// Build course and add it to the list in the User
course c = new course(values);
if (Request.IsAuthenticated)
{
//registeringUser.currentCourses.Add(c);
registeringUser.AddCourse(c);
return RedirectToAction("/Setup4", registeringUser); // <---- This doesn't really work right
//return View();
}
else
{
return Redirect("/Account/Login");
}
}
}
This is my first project with MVC, so if you find that I'm doing the entire thing completely incorrectly, feel free to not answer the question I asked and offer the proper solution to this need. I'm moving an existing (pure) C# project to MVC and I'm mainly just stuck on how to work within MVC's interesting structure. I'm very grateful for any help you can give!
Thanks!
You can store user related data in session without passing it between requests
Smth like this
[HttpPost]
public ActionResult Step1(Step1Model model)
{
Session["UserRegistration"] = new UserRegistration
{
FirstName = model.fName,
....
}
....
}
[HttpPost]
public ActionResult Step2(Step2Model model)
{
var userRegistration = Session["UserRegistration"] as UserRegistration;
if (userRegistration == null) { return Redirrect("Step1"); }
userRegistration.SomeField = model.someField;
...
Session["UserRegistration"] = userRegistration;
....
}

Relationship field is not updated in Edit action (ASP.NET, EF5)

I would really appreciate some insight on this: The following code
[HttpPost]
public ActionResult Edit(BeamCollection beamcollection)
{
if (ModelState.IsValid)
{
beamcollection.BeamMaterial = db.Types.Find(Convert.ToInt32(Request.Form.Get("BeamMaterial_ID")));
db.Entry(beamcollection).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Details", "Bridge");
}
return View(beamcollection);
}
When I attempt to modify a BeamCollection record, all changes are reflected and saved to the DB except for the beamcollection.BeamMaterial which takes the selected value from a DropDownList. When I debug, I can see that the selected value is being assigned to beamcollection.BeamMaterial!
By the way, this field is defined as follows
public virtual AllTypes BeamMaterial { get; set; }
So it reflects a one to many relationship with AllTypes, but it is a unidirectional relationship.
What is kind of strange (to me), is the the same technique is used for the Create action and it perfectly works:
public ActionResult Create(BeamCollection beamcollection)
{
if (ModelState.IsValid)
{
beamcollection.BridgeInfo = db.Bridges.Find(bridgeID);
beamcollection.BeamMaterial = db.Types.Find(Convert.ToInt32(Request.Form.Get("BeamMaterial_ID")));
db.BeamCollections.Add(beamcollection);
db.SaveChanges();
return RedirectToAction("Details", "Bridge");
}
return View(beamcollection);
}
Why is this happening and how to make it work, Please help.
try to use:
db.attach(beamcollection.GetType().Name,beamcollection);
db.ObjectStateManager.ChangeObjectState(beamcollection, EntityState.Modified);
db.SaveChanges();
Thanks to Fabrice's tip I managed to find the correct way, Here is the code:
var currentBeamCollection = db.BeamCollections.Find(beamcollection.ID);
db.Entry(currentBeamCollection).CurrentValues.SetValues(beamcollection);
currentBeamCollection.BeamMaterial = beamcollection.BeamMaterial;
db.SaveChanges();
The logic is as follows: get the original record, update all fields (except for the navigation properties, read below), update the navigation property, finally save.
When I tried to do the following
db.Entry(currentBeamCollection).CurrentValues.SetValues(beamcollection.BeamMaterial);
The system failed with an exception that complains about setting the ID property. I also read that CurrentValues.SetValues() doesn't update navigation properties, and I noticed that the BeamMaterial property was not being updated, so I needed to update it manually.
Thanks Fabrice.

Asp.net MVC Model for view and Layout

I've been trying to find a good way to handle the Models of our Asp.net MVC websites when having common properties for all the pages. These properties are to be displayed in the Layout (Master Page). I'm using a "BaseModel" class that holds those properties and my Layout use this BaseModel as its model.
Every other model inherits from that BaseModel and each has specific properties relative to the view it represents. As you might have guessed, my Models are actually View Models even if that's not quite relevant here.
I have tried different ways to initialize the BaseModel values
By "hand" in every view
Having a base controller that has an Initialize virtual method to do it (so specific controller can implement specific common behavior for exemple)
Having a base controlelr that override OnActionExecuting to call the Initialize method
Using a helper class to do it outside of the controller
Using a Model Factory
But none of those really appeal to me:
Seems obvious to me, but DRY is one reason enough to justify that (actually I never tried that solution at all, I'm just putting it to be able to loop on that point in the last point).
I don't like that one because it means that whenever a new Controller is added, you need to know that it has to inherit from the BaseController and that you need to call the Initialize method, not to mention that if your controller has overriden the base one, to call the base anyway to maintain the values.
see next point
and 3. are a variation on the same topic but that doesn't really help with the issues of the second solution.
My favorite so far, but now I have to pass a few more variables to set those values. I like it for the inversion of dependence. But then if I want to provide values from the session, I need to pass them explicitly for exemple, then I'm back to square one as I have to provide them by hand (being references or through an interface of any kind)
Of course, (almost) all of those solutions work, but I'm looking for a better way to do it.
While typing this question, I found maybe a new path, the builder pattern that might also do, but implementations can become quickly a burden too, as we can have dozens of views and controllers.
I'll gladly take any serious recommandation/hint/advice/patterns/suggestion !
Update
Thanks to #EBarr I came up with another solution, using an ActionFilterAttribute (not production code, did it in 5 minutes):
public class ModelAttribute : ActionFilterAttribute
{
public Type ModelType { get; private set; }
public ModelAttribute(string typeName) : this(Type.GetType(typeName)) { }
public ModelAttribute(Type modelType)
{
if(modelType == null) { throw new ArgumentNullException("modelType"); }
ModelType = modelType;
if (!typeof(BaseModel).IsAssignableFrom(ModelType))
{
throw new ArgumentException("model type should inherit BaseModel");
}
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var model = ModelFactory.GetModel(ModelType);
var foo = filterContext.RequestContext.HttpContext.Session["foo"] as Foo;
model.Foo = foo;
model.Bar = somevalue;
filterContext.Controller.TempData["model"] = model;
}
}
Calling it is then really simple:
[Model(typeof(HomeModel))]
public ActionResult Index()
{
var homeModel = TempData["model"] as HomeModel;
// Add View Specific stuff
return View(homeModel);
}
And it gives me the best of every world. The only drawback is to find a proper way to passe the model back to the action.
Here it's done using the TempData object, but I also consider updating the model that one can find in the ActionParameters.
I'm still taking any serious recommandation/hint/advice/patterns/suggestion for that, or the previous points.
I went through almost exactly the same process as I dove into MVC. And you're right, none of the solutions feel that great.
In the end I used a series of base models. For various reasons I had a few different types of base models, but the logic should apply to a single base type. The majority of my view models then inherited from one of the bases. Then, depending on need/timing i fill the base portion of the model in ActionExecuting or OnActionExecuted.
A snippet of my code that should make the process clear:
if (filterContext.ActionParameters.ContainsKey("model")) {
var tempModel = (System.Object)filterContext.ActionParameters["model"];
if (typeof(BaseModel_SuperLight).IsAssignableFrom(tempModel.GetType())) {
//do stuff required by light weight model
}
if (typeof(BaseModel_RegularWeight).IsAssignableFrom(tempModel.GetType())) {
//do more costly stuff for regular weight model here
}
}
In the end my pattern didn't feel too satisfying. It was, however, practical, flexible and easy to implement varying levels of inheritance. I was also able to inject pre or post controller execution, which mattered a lot in my case. Hope this helps.
The idea that gave me #EBarr to use an action filter was actually working but felt wrong in the end, because there was no clean way to retrieve the model without passing through a viewbag, or the httpcontext items, or something alike. Also, it made mandatory to decorate every action with its model. It also made the postback more difficult to handle. I still believe that this solution has merits and might be useful in some specific scenarios.
So I was back to square one and started looking more into that topic. I came to the following. First the problem has two aspects
Initializing the data for the views
Rendering the data
While looking for more idea, I realized that I was not looking at the problem from the right perspective. I was looking at it from a "Controller" POV, whereas the final client for the model is the view. I was also reminded that the Layout/Master page is not a view and should not have a model associated with it. That insight put me on what feels the right path for me. Because it meant that every "dynamic" part of the Layout should be handled outside of it. Of course, sections seems the perfect fit for that, because of their flexibility.
On the test solution I made, I had (only) 4 different sections, some mandatory, some not. The problem with sections, is that you need to add them on every page, which can quickly be a pain to update/modify. To solve that, I tried this:
public interface IViewModel
{
KeyValuePair<string, PartialViewData>[] Sections { get; }
}
public class PartialViewData
{
public string PartialViewName { get; set; }
public object PartialViewModel { get; set; }
public ViewDataDictionary ViewData { get; set; }
}
For exemple, my model for the view is this:
public class HomeViewModel : IViewModel
{
public Article[] Articles { get; set; } // Article is just a dummy class
public string QuickContactMessage { get; set; } // just here to try things
public HomeViewModel() { Articles = new Article[0]; }
private Dictionary<string, PartialViewData> _Sections = new Dictionary<string, PartialViewData>();
public KeyValuePair<string, PartialViewData>[] Sections
{
get { return _Sections.ToArray(); }
set { _Sections = value.ToDictionary(item => item.Key, item => item.Value); }
}
}
This get initialized in the action:
public ActionResult Index()
{
var hvm = ModelFactory.Get<HomeViewModel>(); // Does not much, basicaly a new HomeViewModel();
hvm.Sections = LayoutHelper.GetCommonSections().ToArray(); // more on this just after
hvm.Articles = ArticlesProvider.GetArticles(); // ArticlesProvider could support DI
return View(hvm);
}
LayoutHelper is a property on the controller (which could be DI'ed if needed):
public class DefaultLayoutHelper
{
private Controller Controller;
public DefaultLayoutHelper(Controller controller) { Controller = controller; }
public Dictionary<string, PartialViewData> GetCommonSections(QuickContactModel quickContactModel = null)
{
var sections = new Dictionary<string, PartialViewData>();
// those calls were made in methods in the solution, I removed it to reduce the length of the answer
sections.Add("header",
Controller.UserLoggedIn() // simple extension that check if there is a user logged in
? new PartialViewData { PartialViewName = "HeaderLoggedIn", PartialViewModel = new HeaderLoggedInViewModel { Username = "Bishop" } }
: new PartialViewData { PartialViewName = "HeaderNotLoggedIn", PartialViewModel = new HeaderLoggedOutViewModel() });
sections.Add("quotes", new PartialViewData { PartialViewName = "Quotes" });
sections.Add("quickcontact", new PartialViewData { PartialViewName = "QuickContactForm", PartialViewModel = model ?? new QuickContactModel() });
return sections;
}
}
And in the views (.cshtml):
#section quotes { #{ Html.RenderPartial(Model.Sections.FirstOrDefault(s => s.Key == "quotes").Value); } }
#section login { #{ Html.RenderPartial(Model.Sections.FirstOrDefault(s => s.Key == "header").Value); } }
#section footer { #{ Html.RenderPartial(Model.Sections.FirstOrDefault(s => s.Key == "footer").Value); } }
The actual solution has more code, I tried to simplify to just get the idea here. It's still a bit raw and need polishing/error handling, but with that I can define in my action, what the sections will be, what model they will use and so on. It can be easily tested and setting up DI should not be an issue.
I still have to duplicate the #section lines in every view, which seems a bit painful (especialy because we can't put the sections in a partial view).
I'm looking into the templated razor delegates to see if that could not replace the sections.

MVC 3 - Widget fine in Create action, but has null values in View action

I'm using ASP.NET MVC3, with Entity Framework.
I have a Widget controller, with standard Widget CRUD actions.
In my Create action, I successfully create a new Widget object, which has two FooBar objects. This is added to my database just fine, and the action the redirects to the View action.
[HttpPost]
public ActionResult Create(Widget model)
{
if (ModelState.IsValid)
{
//At this point, the widget has two FooBar properties. I can see the values for these FooBars just fine.
if (repo.AddWidget(model))
{
ViewBag.Message = "Your widget has been created.");
return RedirectToAction("View", new { id = model.Id });
}
else
{
ViewBag.Error = "Woops, something went wrong. Please try again.");
}
}
return View(model);
}
In the View action, I fetch the newly created Widget from my repository - except now the two FooBar properties are null.
public ActionResult View(int id)
{
var widget = repo.GetWidget(id);
if (widget == null)
{
ViewBag.Error = "No widget found for the specified ID";
return RedirectToAction("Find");
}
//At this point, the widget has two null values for the FooBar1 and FooBar 2 properties
return View(widget);
}
In the database itself I can see the correct FooBar ID values on my Widget.
My model is set up pretty much exactly the same as shown in this tutorial:
http://weblogs.asp.net/scottgu/archive/2010/07/16/code-first-development-with-entity-framework-4.aspx
public class WidgetContext : DbContext
{
public DbSet<Widget> Widgets { get; set; }
public DbSet<FooBar> FooBars { get; set; }
}
Can anyone suggest how I might start tracking this issue down?
Update:
I should clarify the values are null whenever I call the View action, not only after a Create.
Looks like FooBar is separate entity and FooBar1 and FooBar2 are navigation properties. In such case you must either explicitly say you want to loade them (we call this eager loading):
var widget = context.Widgets
.Include(w => w.FooBar1)
.Include(w => w.FooBar2)
.SingleOfDefault(w => w.Id == id);
Note: Strongly typed Include requires EF 4.1 for EFv1 or EFv4 use:
var widget = context.Widgets
.Include("FooBar1")
.Include("FooBar2")
.SingleOfDefault(w => w.Id == id);
or create custom strongly typed extension method like this.
or you must turn lazy loading on. Lazy loading makes separate queries to database once properties are first accessed in your view. It requires making both FooBar1 and FooBar2 virtual and context must be alive when view is rendered. Usually this is handled by singe context per HTTP request where context is for example created and disposed in custom controller factory or in custom Http module.
Also next time make your question complete please. You have shown a lot of code but the important parts (Windget class and GetById method) are missing. Unfortuanatelly users here aren't oracles so we need to now necessary details. Both action methods are almost irrelevant to your problem.

Iterating over an unknown IQueryable's properties?

Forgive me if this has been asked before; I couldn't find anything close after a few searches:
I'm trying to write an ActionFilter in MVC that will "intercept" an IQueryable and nullify all the parent-child relationships at runtime. I'm doing this because Linq does not serialize objects properly if they have parent-child relationships (it throws a circular reference error because the parent refers to the child, which refers back to the parent and so on), and I need the object serialized to Json for an Ajax call. I have tried marking the child relationship in the DBML file with a privacy status of internal, and while this fixes the serialization problem, it also hides the child members from the view engine when the page renders, causing another error to be thrown. So, by fixing one problem, I cause another.
The only thing that fixes both problems is to manually set the child members to null just before returning the serialization, but I'm trying to avoid doing that because it's cumbersome, not reusable, etc. I'd rather use an ActionFilter to inspect the IQueryable that is being serialized and nullify any members with a Type of EntitySet (how Foreign Keys/Associations are represented). However, I don't have much experience with Reflection and can't find any examples that illustrate how to do something like this. So... is this possible with Reflection? Is there a better way to accomplish the same thing? I'll post the relevant code tomorrow when I'm back at my work computer.
Thanks,
Daniel
As promised, the code:
[GridAction]
public ActionResult _GetGrid()
{
IQueryable<Form> result = formRepository.GetAll();
foreach (Form f in result)
{
f.LineItems = null;
f.Notes = null;
}
return View(new GridModel<Form> { Data = result });
}
An added wrinkle is that I'm using the new Telerik MVC Extensions, so I'm not actually serializing the Json myself -- I'm just returning the IQueryable in an IGridModel, and the action filter [GridAction] does the rest.
So, just in case anyone's curious, here's how I finally solved this problem: I modified Damien Guard's T4 template to include the attribute [ScriptIgnore] above entities of type Association. This lets the JSON serializer know to not bother serializing these, thus preventing the circular reference problem I was getting. The generated code ends up looking like this:
private EntitySet<LineItem> _LineItems;
[ScriptIgnore]
[Association(Name=#"Form_LineItem", Storage=#"_LineItems", ThisKey=#"Id", OtherKey=#"FormId")]
public EntitySet<LineItem> LineItems
{
get {
return _LineItems;
}
set {
_LineItems.Assign(value);
}
}
This fixes the serialization problem I was having without disabling the use of child tables through LINQ. The grid action on the controller ends up looking like this:
[GridAction]
public ActionResult _GetGrid()
{
return View(new GridModel<Form> { Data = formRepository.GetAll() });
}
There are two options, one is to ignore those properties during serialization using [XmlIgnore]. The other one is to nullify the properties using reflection.
Ignore in serialization, simple usage sample that shows how to use default value in serialization:
[Serializable]
public class MyClass
{
[XmlIgnore]
public int IgnoredVal { get; set; }
public int Val { get; set; }
}
public void UsageSample()
{
var xmlSerializer = new XmlSerializer(typeof(MyClass));
var memoryStream = new MemoryStream();
var toSerialize = new MyClass { IgnoredVal = 1, Val = 2 };
xmlSerializer.Serialize(memoryStream, toSerialize);
memoryStream.Position = 0;
var deserialize = (MyClass)xmlSerializer.Deserialize(memoryStream);
Assert.AreEqual(0, deserialize.IgnoredVal);
Assert.AreEqual(2, deserialize.Val);
}
Nullify with reflection, code sample:
public void NullifyEntitySetProperties(object obj)
{
var entitySetProperties = obj.GetType().GetProperties()
.Where(property => property.PropertyType == typeof(EntitySet));
foreach (var property in entitySetProperties)
{
property.SetValue(obj, null, null);
}
}
In my opinion, if the first option can be done used in your code it's better. This option is more direct and economic.

Resources