Unable to update MVC3 Model - InvalidOperationException - asp.net-mvc-3

I am fairly new to prgramming in general. I have a database with 3 tables in it. A table called User with a foreign Key TypeID null, a table called Login that has a foreign key UserID null and TypeID not null, and a table called Types with TypeID as the primary key. When I tried to update my entity Model, I receive an Invalid InvalidOperationException.
at System.Data.Objects.ObjectStateManager.ChangeObjectState(Object entity, EntityState entityState)
at MBMVCApplication.Controllers.LoginController.Edit(Users user, Int32 TypeID) in C:\Development\MBMVCApplication\Controllers\LoginController.cs:line 130
at lambda_method(Closure , ControllerBase , Object[] )
at System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters)
at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters)
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters)
at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass15.<InvokeActionMethodWithFilters>b__12()
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation)
Code
[HttpPost]
public ActionResult Edit(Login login, int? typeID)
{
if (ModelState.IsValid)
{
db.Logins.Attach(login);
//TypeID display on a dropdown box
//I also remove the two line below and still get the error
login.User.TypeID = typeId.Value;
db.ObjectStateManager.ChangeObjectState(login, EntityState.Modified); //The error occurs here or
db.SaveChanges(); //Here
return RedirectToAction("Index");
}
ViewBag.UserID = new SelectList(db.Users, "UserID", "FirstName", login.UserID);
ViewBag.TypeID= new SelectList(db.Types, "TypeID", "Description", login.TypeID);
return View(login);
}

I had to manually updated each table. I ran a query to obtain the primary ID; then, I created a new object for the model passing all the values that were selected from the view and updated the table using the primary key. Any table that had a foreign key associated with that table had to be updated manually. It was time consuming and may not be efficient, but it happens to be the workaround.

Related

Validate kendoUI DataSourceRequest object in c#

I have a tool that's feeding a bunch of gibberish at my site for security purposes, but one thing that is erroring my system out is the DataSourceRequest object in the controller. for a sample piece of code:
[AcceptVerbs(HttpVerbs.Get)]
public async Task<ActionResult> _GetGoodData([DataSourceRequest] DataSourceRequest request)
{
List<GoodData> reqs = await GetGoodDataAsync();
return Json(reqs.ToDataSourceResult(request, ModelState), JsonRequestBehavior.AllowGet);
}
I will get an error like:
Invalid property or field - '19451827' for type: GoodData Type =
System.ArgumentException Source = Kendo.Mvc at
Kendo.Mvc.Infrastructure.Implementation.Expressions.MemberAccessTokenExtensions.CreateMemberAccessExpression(IMemberAccessToken
token, Expression instance) at
Kendo.Mvc.Infrastructure.Implementation.Expressions.ExpressionFactory.MakeMemberAccess(Expression
instance, String memberName) at
Kendo.Mvc.Infrastructure.Implementation.Expressions.PropertyAccessExpressionBuilder.CreateMemberAccessExpression()
at
Kendo.Mvc.Infrastructure.Implementation.Expressions.MemberAccessExpressionBuilderBase.CreateLambdaExpression()
at
Kendo.Mvc.Infrastructure.Implementation.SortDescriptorCollectionExpressionBuilder.Sort()
at
Kendo.Mvc.Extensions.QueryableExtensions.CreateDataSourceResult[TModel,TResult](IQueryable
queryable, DataSourceRequest request, ModelStateDictionary modelState,
Func`2 selector)
Now my question here is, is there a way to validate the request object to see if it's fields are valid to the object that it's being applied to?

Update multiple fields of JPA Entity

I have JPA entity Customer having say 50 fields and I would like to update it from the end user using html form.
I am passing one instance of entity to html page form (using thymeleaf), this form is having only 20 fields out of 50 (including ID field). Now once the form is submitted, I would like to update 20 fields from data received using form to the database. I am not getting solution for above issue. One solution is to update individual field but I don't think it is good solution.
#Entity
public class Customer
{
...
50 fields
}
My get method:
#GetMapping(value = "customer")
public String customer(Model model) {
Customer customer = null;
Optional<Customer> optional = customer Repo.findById(customerId);
if (optional.isPresent()) {
customer = optional.get();
}
model.addAttribute("customer", Customer);
return "customer";
}
Html form:
<form action="updateCustomer">
----20 fields which I would like to get update from user are here
</form>
#PostMapping(value = "updateCustomer")
public String updateCustomer(Model model, #ModelAttribute Customer customer) {
if(customer==null) {
System.out.println("Customer object is null");
}
else
{
customerRepo.save(customer);
}
return "savedCustomer";
}
In the post method when I get customer object it is having only 20 fields data not 50(Customer entity is having total fields) because html form is having only 20 fields for update. How to update the old customer object having 50 fields using the new customer object having updated 20 fields.?
There are three ways in the past that I solved this problem
1) have the page GET the Customer object in question, use the object to pre-populate the form, and then POST the changed customer object. The benefit is that the user changing the Customer sees all info related to the Customer, and you have a easy merge on the backend. The drawback is an additional REST call.
2) Create a DTO, and transfer non-null fields from the DTO to the entity. The benefit is you don't have to update all the fields in the form, and no extra network call. the drawback is that it's a pure pain in the rear end.
3) Create a DTO, and make it an entity to save. The benefit is that it's a easy merge back to the database, nothing prevents you from mapping the same table and fields to multiple entities. The drawback is that you have to worry about concurrency issues, which may just not work in your workflow, and the DTO is basically specific per form.
To make partial updates to entity, you either need to use Criteria API or JPQL query ... this is called projection in JPA, here is a quick example ,
Note : You might not be able to use this feature if you are using an old version of JPA query parser (no JPQL updates) + old version of JPA (when no CriteriaUpdate lib was there) you will then need to fetch the object from DB with the id passed from the client, update the 20 properties from the client and save the changes back
Below solution worked for me:
Helper Class:
public class BeanCopy {
private static final Set<Class<?>> primitiveTypes = new HashSet<Class<?>>(
Arrays.asList(Boolean.class, Character.class, Byte.class, Short.class, Short.class, Integer.class, Long.class, Float.class, Double.class, Void.class, String.class, Date.class));
public static void nullAwareBeanCopy(Object dest, Object source) throws IllegalAccessException, InvocationTargetException
{
new BeanUtilsBean() {
#Override
public void copyProperty(Object dest, String name, Object value)
throws IllegalAccessException, InvocationTargetException {
if(value != null && (primitiveTypes.contains(value.getClass()) )) {
super.copyProperty(dest, name, value);
}
}
}.copyProperties(dest, source);
}
}
This is how I copied and forced changes to database:
try {
BeanCopy.nullAwareBeanCopy(DBcustomer,customer);
customerRepo.save(DBcustomer);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
Please let me know any better solution is available for the above problem.

Abort action execution in BindModel method

I have implemented custom model binder in my File Upload Action. Sometimes file upload is dropped by server and BindModel method is called with partial data (ContentLenght and TotalBytes do not match here). I would like to abort Action execution from custom model binder, how to do that?
public class OptionModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var optionModelName = GetOptionModelName(controllerContext);
if (optionModelName != null) return null// !!!How to abort Action execution?!!! here
Trace.TraceInformation(optionModelName);
var model = System.Reflection.Assembly.GetExecutingAssembly().CreateInstance(optionModelName);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType());
return base.BindModel(controllerContext, bindingContext);
}
public class OptionModelBinderAttribute : CustomModelBinderAttribute
{
public override IModelBinder GetBinder()
{
return new OptionModelBinder();
}
}
[HttpPost]
public ActionResult UploadFile(IEnumerable<HttpPostedFileBase> clientUpload, [OptionModelBinder]IOptionViewModel formData)
{
}
This is not something you want to do from the model binding.
The model binding should not control logic behavior. it does not make sense.
I suggest in the controller, you'll ask if something is null and return the appropriate result to the client.
It is not right to let the model binding do the controller's work.

Invalid ModelState with null parameters

Playing with the new WebAPI 2.0 RC1 prerelease bits... given this method:
[HttpPut("{sampleForm}/{id?}")]
public HttpResponseMessage PutSampleForm(SampleForm sampleForm, int? id)
{
if (!ModelState.IsValid)
{
// handle invalid model
}
// Insert valid model into DB with EF
return Request.CreateResponse(HttpStatusCode.OK);
}
It is marked with nullable id, but if id is in fact null, the ModelState is flagged as invalid as well... is this expected, or is there something I can do to let the ModelState know it needs to ignore nullable parameters?
yes you would use question mark on the property of your model also, like this:
private int? id {get; set;}

How to bind a <input type = "text" .. with my property in ViewMode

How to bind to an input[type='text'] field with a property of type Subsidiary
When user register a party, one of the fields is the subsidiary that was the party
Instead of putting a DropDown, Select or Radio I put a input[type='text'] field
and when the user starts typing the name of the subsidiary, the autocomplete of jQueryUI shows the list of subsidiaries have filtered
Code
To accomplish these tasks, I have:
ViewModel
public class PartyViewModel
{
[UIHint("SubsidiarySelect")]
public Subsidiary Subsidiary { get; set; }
}
HTML
HTML generated by EditorTemplates of SubsidiarySelect
<input id="Subsidiary_Title" name="Subsidiary.Title" type="text" value="">
<input id="Subsidiary" name="Subsidiary" type="hidden" value="00000000-0000-0000-0000-000000000000">
#Subsidiary_Title > is used to display the selected subsidiary
#Subsidiary > saves the selected code Guid of subsidiary
Controller
My control is nothing special.
I would like the property Subsidiary in my PartyViewModel class would have filled.
[HttpPost]
public ActionResult Nova(PartyViewModel model)
{
if (ModelState.IsValid)
{
//.....
}
}
Questions
I thought of creating a SubsidiaryBinder: IModelBinder so that when the post was made, I would fill the Subsidiary property with the Database values (as have the ID)
If this is the solution, then how to create a binder to run only in the class PartyViewModel
How to automatically retrieve the values ​​from the database for the Subsidiary property class PartyViewModel when making a post?
You can set the Binder for your PartyViewModel on Application_Start in global.asax
Sample
Your Binder
public class PartyViewModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// ... do something,
// for example, retieve values from database
return base.BindModel(controllerContext, bindingContext);
}
}
Register in global.asax
ModelBinders.Binders.Add(typeof(PartyViewModel), new PartyViewModelBinder());

Resources