EF4 STE's and MVC2 - ChangeTracker not POSTing - c#

I'm trying to set up an MVC2 app with Entity Framework Self-Tracking-Entities. My view is a strongly-typed view of the entity type generated by the STE T4 template. Here's my GET:
public ActionResult Edit(int id)
{
var ri = new App1Service.App1Client().GetMyObj(id);
var changeTracking = ri.ChangeTracker.ChangeTrackingEnabled; // this shows true
return View(ri);
}
So far, so good. When this form POSTs, however, ChangeTracker is null / reset to default values, and thus, the STE seems suddenly to forget that it's supposed to be tracking itself:
[HttpPost]
public ActionResult Edit(MyObj ri)
{
// MyObj.ChangeTracker.ChangeTrackingEnabled now shows false
// so the following line doesn't save anything:
new App1Service.App1Client().SaveMyObj(ri);
return RedirectToAction("Index");
}
What's the secret to getting the strongly-typed-view to hang onto (and POST back) the whole STE? If I have to go and do another read to get original values and then apply changes, that seems to defeat the purpose of self-tracking-entities, doesn't it?

Related

ASP.NET MVC 5 Keeping old input between requests

I need feature that is something similar to Laravel's old input helper but in MVC 5.
https://laravel.com/docs/5.6/requests#old-input
If validation fails, I need to reload all my model data as it was in the previous request except those inputs where user entered something wrong.
The problem is that my form has many disabled inputs and fields that program is fetching within [HttpGet] method, and they're getting lost during submission. So I need to store them in session.
The code below seems to work but is there any more efficient and beautiful way to do so with a less amount of code within each controller?
[HttpGet]
[Route(#"TaskManagement/Edit/{guid}")]
public async Task<ActionResult> Edit(Guid guid)
{
var model = new EditTaskViewModel();
model.Guid = guid;
await model.GetTaskFromRemoteService(new UserInfo(User));
ControllerHelpers.DisplayAlerts(model, this);
TempData["OldModel"] = model;
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
[Route(#"TaskManagement/Edit/{guid}")]
public async Task<ActionResult> Edit(EditTaskViewModel model, Guid guid, string submit)
{
model.Guid = guid;
if (ModelState.IsValid) {
await model.UpdateTaskInRemoteService(new UserInfo(User), submit);
ControllerHelpers.DisplayAlerts(model, this, "Task successfully updated");
if (model.ErrorCode == null)
return RedirectToAction("Edit", new { guid = model.Guid });
return RedirectToAction("Index");
}
if (TempData["OldModel"] != null) {
model = (EditTaskViewModel)TempData["OldModel"];
}
return View(model);
}
Using session state (including TempData) like this may break when you have multiple copies of the page open. You can work around this by generating a unique ID for the session key and storing it in a hidden field.
However, I would try to avoid using session altogether.
A simple approach is to use hidden fields to store the values that aren't sent to the server because they are in disabled fields.
A more robust approach is a separate class (or at least a private method) that knows how to setup your model for the first time and in transition (e.g. failed server validation). I call these classes "composers" and I describe the approach here.
Pseudocode for how an action method with a composer might look:
if( ModelState.IsValid ){
return Redirect();
}
var rebuiltModel = _composer.ComposeEdit( incomingModel );
return View( rebuiltModel );
I think the answer was quite simple. The shortest and easiest way is to populate the object from the database\remote service once more.
The fields that user entered whether they're valid or not will stay as they were before. The rest of them will load once again.

Saving Errors When Clearing a Form (MVC)

I'm new to using MVC 5 and .NET framework 4.5 and I have encountered an issue. I have a form, and I want to reset the fields of the form when it errors out, but keep the errors and display to the user. Currently I can clear the fields on error, but this also gets rid of the errors. I have tried
originalModel.field= "";
but this doesn't clear the field. I have also tried just using
ModelState.Clear();
But this doesn't do anything either.
This is what I'm currently working with (which clears everything):
if (!ModelState.IsValid)
{
TestModel blank= new TestModel();
ModelState.Clear();
return View("View.cshtml", blank);
}
If you absolutely need this behavior, you can go and set the value of this property of your view model to empty string in the Modelstate dictionary.
[HttpPost]
public virtual ActionResult Index(CreatePostVm model)
{
if (ModelState.IsValid == false)
{
ModelState["PostTitle"].Value =
new ValueProviderResult(string.Empty, string.Empty,CultureInfo.InvariantCulture);
return View(model);
}
// to do : Return something
}
The above code will set the PostTitle property of your CreatePostVm view model to an empty text. So in your view, the input field will be an empty text while still displaying the validation error message for PostTitle field.

How can I add a database entry using a viewmodel?

I am making a page that will contains several dropdowns and a few textboxes. I have the data being stored in a viewmodel, that is then passed back to my [HttpPost] create method.
I am fairly new to viewmodels, and am wondering how to add a new database entry using data from a viewmodel.
When I go into Debug mode, all my viewmodel entities are populated, but MVC is not making the connection to make a new entry with those values. Current [Post] create method.
[HttpPost]
public ActionResult Create(OrdersViewModel vmobj)
{
if (ModelState.IsValid)
{
order obj = new order();
obj.projectNumber=vmobj.projectID.ToString();
obj.testingPhase = vmobj.testPhaseSt;
obj.unitSerial = vmobj.unitSerialSt;
obj.completeDate = vmobj.completeDate;
obj.closingStatement = vmobj.closingStatement;
db.orders.Add(obj);
return RedirectToAction("Create");
}
return View();
}
You need to call the DbContext.SaveChanges() to apply the changes in the database. Just like Jeroen told you.

How to update just some of properties in Edit action

Preface:
I'm working on a project using MVC 5. We've built up our database from which our model is created. Then, we've added the Controllers and Views using Scaffolding. However in the Views we've omitted some of the properties (they should not be shown to the users).
Problem:
In the actions like Edit, as I hit the save button (to update the model) I encounter an exception and I think it requires me to provide the value of all the properties.
Please let me know how can I update just some of the properties (shown in the view)?
Please note that I need a solution as general as possible, so as to be able to use it in many Edit actions that I have in this project (Indeed this is the hard part).
Code:
The following are some of the codes that I think are most related to this question:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "AreaCode,Tels,Address")] Area area)
{//Area has many more properties (defined as required in the database) and I just need to update
//these : AreaCode,Tels,Address
if (ModelState.IsValid)
{
db.Entry(area).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.Detector = new SelectList(db.Detectors, "DetectorCode", "detectorName", area.Detector);
ViewBag.Grade = new SelectList(db.Grades, "Gradeid", "GradeName", area.Grade);
return View(area);
}
SS:
Answers expressed in a simple way are highly appreciated.
Get your object to update from the database, and sets your model with the old value of properties
public ActionResult Edit([Bind(Include = "AreaCode,Tels,Address")] Area area)
{
YourDbContext _db= new YourDbContext();
Area oldArea = _db.Areas.Where(x => x.ID == area.ID).FirstOrDefault();
// Bind others properties marked with required from database
area.x = oldArea.x;
area.y = oldArea.y;
if (ModelState.IsValid)
{
db.Entry(area).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.Detector = new SelectList(db.Detectors, "DetectorCode", "detectorName", area.Detector);
ViewBag.Grade = new SelectList(db.Grades, "Gradeid", "GradeName", area.Grade);
return View(area);
}

ASP.Net MVC Viewmodels How to map values from a viewmodel to the database tables

I'm trying to use ViewModels and AutoMapper, as I know these are best practice for avoiding lots of issues.
I'm ok using AutoMapper to populate a viewmodel, but I'm not as sure about how to update my database, from the ViewModel being posted back to my controller.
My GET is:
public ActionResult Edit(int id = 0)
{
Customer customer = db.Customers.Find(id);
var offers = db.Offers.Where(x => x.CustomerId == id).ToList();
var email = db.Emails.FirstOrDefault();
var vm = new CreateViewModel();
vm.CustomerId = customer.CustomerId;
vm.ArrivalDate = customer.ArrivalDate;
vm.CustomerName = customer.CustomerName;
vm.EmailAddress = customer.EmailAddress;
vm.NumNights = customer.NumNights;
vm.NumPeople = customer.NumPeople;
vm.EmailBody = email.EmailBody;
vm.From = email.From;
vm.Subject = email.Subject;
// Map list of Offers into ViewModel
vm.Offers = Mapper.Map<IList<Offer>, IList<OfferVM>>(offers);
return View(vm);
}
My POST is:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(EditViewModel vme)
{
if (ModelState.IsValid)
{
// Update the Customer properties
Customer customer = db.Customers.Find(vme.CustomerId);
customer.NumPeople = vme.NumPeople;
customer.NumNights = vme.NumNights;
customer.ArrivalDate = vme.ArrivalDate;
customer.CustomerName = vme.CustomerName;
customer.EmailAddress = vme.EmailAddress;
// Update Offers table
foreach (var o in vme.Offers)
{
// find the offer
Offer offer = db.Offers.Find(o.OfferId);
if (offer != null)
{
// update the properties of Offer
offer.RoomRate = o.RoomRate;
offer.IncludeInOffer = o.IncludeInOffer;
}
}
db.SaveChanges();
return RedirectToAction("Index");
}
return View(vme);
}
So my Post is manually updating two database tables (Offers and Customers).
The GET method is elegant, using AutoMapper, the POST is not. I'm wondering if there is a more straightforward way of updating the database via AutoMapper, without having to manually go through each property I am looking to update? Or is my POST controller as efficient as it can be?
Thank you for any pointers,
Mark
There is an overload of Map that allows to map properties to one preexisting instance:
Mapper.Map<IObjectA, IObjectB>(objectA, ObjectB);
Thus, you just have to map from the parameter vme (CreateViewModel vme) to the customer recovered from the DB. This also applies to the offers part.
Of course, you'll have to configure the mappings in the reverse direction: you've done it from db objects to view model, and now you need to map them from viewmodel to db objects. If you've followed Automapper conventions it will be pretty easy or even unnnecessary.
EDIT: added interesting comment by Henk Mollema
You can configure AutoMapper to ignore properties, if your ViewModel doesn't contain all the properties from your domain model (which is most likely the case), it won't overwrite them with nulls.
You first need to define your mapping like this:
Mapper.CreateMap<EditViewModel, Customer>();
Your updated action method could look like this. What happens here is your action method receives your view model and checks its validity. If it is valid then it does the mapping from your view model to your domain model. This domain model is then passed to your service or repository layer to update the cusotmer in the database. How you update the record depends entirely on you.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(EditViewModel viewModel)
{
if (!ModelState.IsValid)
{
return View(viewModel);
}
Customer customer = Mapper.Map<Customer>(viewModel);
customerService.Edit(customer);
return RedirectToAction("List");
}
I hope this can help guide you in the right direction. Your HttpGet action method will work in a similar way.

Categories