How to find reason "A circular reference was detected while serializing.... 'System.Reflection.RuntimeModule' - asp.net

I have setup the view MyView.chstml with two submit action. but give me "A circular reference was detected while serializing an object of type 'System.Reflection.RuntimeModule'. How to troubleshoot easily ?
#using (Html.BeginForm("MyView", "Worker", FormMethod.Post, new { enctype = "multipart/form-data", #name = "formWorker" }))
{
#Html.AntiForgeryToken()
<div class="form-horizontal" id="divWork"> ....
<div class="form-group">
<button id="btnSubmit" class="btn btn-success" type="submit" value="Work1">Work1</button>
</div>
<div id="Dynamictable" class="table-responsive hidden" name="dtable">
<table class="table table-bordered" id="dctable" name="dctable"></table>
</div>
<div id="dialogsubmit" title="second submit">
<div id="dialog-content" name="dialog-content" class="form-control hidden"> </div>
</div>
</div>
then in script
(function () {
//strangely the below ajax is never called
$('#formWorker').submit(function () {
debugger;
$.ajax({
url: this.action,
type: this.method,
data: $(this).serialize(),
success: function (result) {
debugger;
bootbox.alert(result);
}
});
// it is important to return false in order to
// cancel the default submission of the form
// and perform the AJAX call
return false;
});
}
$("#btnSubmit").click(function () {
if(document.getElementById('dctable').getElementsByTagName("tr").length < 1)
{
aRow = document.all("dctable").insertRow();
var oCell = aRow.insertCell();
oCell = newRow.insertCell();
oCell.innerHTML = '<input type="submit" name="submit2" value="submit2" />';
**//strangely if i replace the above RHS with below, it act as submit halft submit form (half becuase the FormCollection object in HttpPost method of controller lacks key submit2 and also HttpPostedFileBase object came as null in controller method ,**
*//'<button id="submit2" name="submit2" class="btn submit2" value="submit2" ><i class="fa fa-search"></i> submit2</button>'*
return false;
}
});
</script>
in controller
[HttpGet]
public ActionResult Worker()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Worker(HttpPostedFileBase file, FormCollection form)
{
string value = form["submit2"]; //half submit form as it is null in additon to file is null. if i use commented RHS , all good here, but no option in view to process the output in same view.
IEnumerable<object> data = Enumerable.Empty<object>();
if (value != null) // key doesn't exist
{
//process here and and return json to shown result on same page using popup/alert.
return this.Json(
new
{
Result = data.ToList()
}, JsonRequestBehavior.AllowGet
);
}
return PartialView("anoterhView", data.ToList());
}

A circular reference exception is generally thrown when the JSON serializer initiates a loop that causes effectively an infinite loop of serializing. This happens when you look at the schema of your serializable properties. For example:
Take the two classes below:
public class Node
{
public Node Parent { get; set; }
}
public class Parent : Node
{
public List<Node> Children { get; set; } = new List<Node>();
}
As you can see we have two simple classes, a Node class which has a public property Parent which is type Node. Now if we look at the Parent class it derrives from Node and has the property Children which is a List<Node>. This is where we will start to have circular dependency issues. Consider the following simple method.
public string SerializeJsonObject()
{
var parent = new Parent();
var child = new Node();
child.Parent = parent;
parent.Children.Add(child);
return Json(parent);
}
This method constructs a Parent object parent and then a Node object child. Next we set the Parent property of child to the parent instance. Then we Add the child to the parent's Children list.
Now consider the serialization of parent.
-- Parent Item
-- Parent: null no procesing
-- Children : Serialize each Node object
-- Node 1
-- Parent
-- Parent: null no processing
-- Children:
-- Node 1
-- Parent
--Parent: null no processing
--Children:
-- Node 1
..... continues forever never finishing serializing Node 1
From this we can see our circular dependency is the Parent property however it is only a circular reference because then Parent has a reference to the Node in the Children collection. Therefore the serializer can never finish serializing the object.
Now this isn't limited to lists we can look at a similar example where two classes have a reference to each other and are both serializable. Consider the following class.
public class Node
{
public string Name { get; set; }
public Node Previous { get; set; }
public Node Next { get; set; }
}
This class has a dependency on Node for both the Previous and Next property. Therefore given a method of constructing a small dataset.
public static object SerailizeANode()
{
Node first = null;
Node previous = null;
for(var i = 0; i < 10; i++)
{
var current = new Node();
current.Name = $"Node {i}";
if(previous != null)
{
previous.Next = current;
current.Previous = previous;
}
previous = current;
if (first == null)
first = current;
}
return Json(first);
}
This is really simple but ends up with 10 objects where 1-9 have a dependency on the Next node and objects 2-10 have a dependency to the Previous node. So given the serialization of first
-- first
-- Name: Node 0
-- Previous: Null
-- Next:
-- Name: Node 1
-- Previous
-- Name: Node 0
-- Previous: null
-- Next:
-- Name: Node 1
-- Previous:
-- Name: Node 0
-- Previous: null
-- Next:
--Name: Node 1
continues on forever.
Again as we see by the dependency serialization of the Property (Previous & Next) cause the serializer to hit a circular reference (infinite loop) and throws an exception.
My expectation is there a similar issue in the data that is returning to the browser through the section nicely commented out.
//process here and and return json to shown result on same page using popup/alert.
return this.Json(new
{
Result = data.ToList()
}, JsonRequestBehavior.AllowGet);
If you need more information could you please post the schema of the classes being returned in the commented out section.

Related

ASP.NET MVC: How to send the ids of the DOM Body elements on the client's browser TO the controller when navigating from the view

I am working on an ASP.NET MVC app (ASP.NET NOT ASP.NET Core).
When a View is rendered, the user can click on some buttons on the page to collapse or show divs associated with each button. The div changes its class depending on whether it is collapsed or shown. I am using bootstrap attributes for this, and it works fine.
Now I have a "Save" button on the page. When the user clicks on this button, I need to retrieve the ids and classes of the divs, and pass them TO the Controller (in an array/collection/dictionary whatever).
Is there a way/method in ASP.NET to send to the Controller the attributes (ids, classes, etc) of the DOM elements on the client's browser ?
Thanks
If you want to send some attributes of DOM to Controller, I have a way.
HTML:
<div id="demo-1" class="chosendiv other-className" data-code ="abc">Lorem Ipsum</div>
<div id="demo-2" class="chosendiv other-className" data-code ="xyz">Lorem Ipsum</div>
<div id="demo-3" class="other-className" data-code ="mnt">Lorem Ipsum</div>
<button id="btn-save" onclick="Save()">SAVE</button>
Javascript
<script>
function Save(){
var cds = document.getElementsByClassName('chosendiv');
var finder = [];
if(cds != null){
for(i = 0; i< cds.length; i++){
finder.push({
ID: cds[i].getAttribute('id'),
ClassName: cds[i].getAttribute('class'),
Code: cds[i].getAttribute('data-code')
})
}
}
//
// Send finder to Controller. You can use Ajax...
// A simple ajax call:
//
$.ajax({
url: '/Home/YourAction',
type: 'GET', //<---- you can use POST method.
data:{
myDiv: JSON.stringify(finder)
},
success: function(response){
// Your code
}
})
}
</script>
Your Controller
public class HomeController: Controller
{
public HomeController(){}
[HttpGet]
public void YourAction(string myDiv)
{
//A lot of ways for converting string to Object, such as: creating new class for model, ...
// I use Dictionary Class
List<Dictionary<string, string>> temp = new List<Dictionary<string, string>>();
if(!string.IsNullOrEmpty(myDiv))
{
try
{
temp = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Dictionary<string, string>>>(myDiv);
}
catch { // Do something if it catches error. }
}
// Get a element (at index) from temp if temp.Count()>0
// var id = temp.ElementAt(index)["ID"];
// var className = temp.ElementAt(index)["ClassName"];
// var code = temp.ElementAt(index)["Code"];
//
//Your code
//
}
//......
}
It would be great if my answer could solve your problem.
Based on the answer provided by #Gia Khang
I made few changes in order to avoid the issue of the length of the URL exceeding the maximum limit.
Instead of adding the element's classes to an array using JS, I add them to a string :
function Save() {
var cds = document.getElementsByClassName('chosendiv');
// I use as string instead of an array
var finder = "";
if(cds != null){
for(i = 0; i< cds.length; i++){
finder = finder + "id=" + cds[i].getAttribute('id') + "class=" + cds[i].getAttribute('class') + "data-code=" +cds[i].getAttribute('data-code')
}
}
// Send finder to Controller. You can use Ajax...
// A simple ajax call:
var myURL = "/{Controller}/{Action}"
$.ajax({
url: myURL,
type: "POST",
data: { ids:finder },
success: function (response) {
}
})
}
In the Controller Action I add a parameter named "ids" (this must be the same name as the identifier of the data object in the post request)and I extract the id, class, and data value from the ids string by a method in one of my Models classes (sorry I work with VB.NET not with C# and it will take me a lot of time to convert the code to C#. I use the Split method in VB to split the ids string several times: a first one by using "id=" as delimiter, then spiting each element in the resulting array by the second delimiter "class=", etc. I add the resulting elements to a collection)
The Controller Action looks like this:
public class HomeController: Controller
{
public HomeController(){}
[HttpPost]
public void YourAction(string ids)
{
Models.myClass.splitStringMethod(ids)
Return View()
}
}

Localization in EnumDropDownListFor using asp.net boilerplate

I have an enum dropdown
//control
#Html.EnumDropDownListFor(
m => m.OrderBy,
new {#class = "btn btn-default dropdown-toggle toggle", onchange = "document.getElementById('hf_Pagename').value,this.form.submit();"})
//my enum
public enum OrderByOptions
{
Default,
PriceLowToHigh,
PriceHighToLow,
MostRecent
}
Now the problem is I need to localize them. But in this case from" PriceLowToHigh" needs to change to " Price- low to high"
You can use AbpDisplayNameAttribute:
public enum OrderByOptions
{
[AbpDisplayName(MyConsts.LocalizationSourceName, "OrderByOptions.Default")]
Default,
[AbpDisplayName(MyConsts.LocalizationSourceName, "OrderByOptions.PriceLowToHigh")]
PriceLowToHigh,
[AbpDisplayName(MyConsts.LocalizationSourceName, "OrderByOptions.PriceHighToLow")]
PriceHighToLow,
[AbpDisplayName(MyConsts.LocalizationSourceName, "OrderByOptions.MostRecent")]
MostRecent
}
Define them in your localization files:
<text name="OrderByOptions.PriceLowToHigh">Price - Low to High</text>
Update
AbpDisplayName works on type class
You can define:
[AttributeUsage(AttributeTargets.Field)]
public class FieldAbpDisplayNameAttribute : AbpDisplayNameAttribute
{
// ...
}
Then use [FieldAbpDisplayNameAttribute(...)] instead.
There are many ways to achieve the issue.
Way #1
Don't use #Html.EnumDropDownListFor! Just traverse enum and create the html element like below;
(I am writing the code on the top of my head)
<select>
#foreach (var item in Enum.GetValues(typeof(OrderByOptions)))
{
<option value="#((int)item)">#(Localize(item.ToString()))</option>
}
</select>
There's no Localize method. Just localize it with your way.
Way #2
Other alternative is not using enum but create a dropdown item collection. And let an item consist of DisplayText and Value. Display Text must be localized from server.
Way #3
Follow the instructions explained here:
https://ruijarimba.wordpress.com/2012/02/17/asp-net-mvc-creating-localized-dropdownlists-for-enums/
With the information above, I solve my problem this way.
I created a custome Attribute AbpEnumDisplayNameAttribute inherited from AbpDisplayNameAttribute.
[AttributeUsage(AttributeTargets.Field)]
public class AbpEnumDisplayNameAttribute : AbpDisplayNameAttribute
{
/// <summary>
/// <see cref="AbpDisplayNameAttribute"/> for enum values.
/// </summary>
public AbpEnumDisplayNameAttribute(string sourceName, string key) : base(sourceName, key)
{
}
}
Then I created an extension for Enum display value localization.
public static class EnumLocalizationExtension
{
public static string ToLocalizedDisplayName(this Enum value)
{
var displayName = value.ToString();
var fieldInfo = value.GetType().GetField(displayName);
if (fieldInfo != null)
{
var attribute = fieldInfo.GetCustomAttributes(typeof(AbpEnumDisplayNameAttribute), true)
.Cast<AbpEnumDisplayNameAttribute>().Single();
if (attribute != null)
{
displayName = attribute.DisplayName;
}
}
return displayName;
}
}

How to skip displaying a content item in Orchard CMS?

I have a content part that provides a begin timestamp and end timestamp option. These 2 fields are used to define a period of time in which the content item should be displayed.
I now have difficulties to implement a skip approach whereas content items should not be displayed / skipped when the period of time does not span the current time.
Digging in the source code and trying to find an entry point for my approach resulted in the following content handler
public class SkipContentHandler : Orchard.ContentManagement.Handlers.ContentHandler
{
protected override void BuildDisplayShape(Orchard.ContentManagement.Handlers.BuildDisplayContext aContext)
{
if (...) // my condition to process only content shapes which need to be skipped
{
aContext.Shape = null; // return null shape to skip it
}
}
}
This works but there are several side effects
I had to alter the source code of BuildDisplayContext as the Shape is normally read only
List shape may displayed a wrong pager when it contains content items with my content part because the Count() call in ContainerPartDriver.Display() is executed before BuildDisplay()
calling the URL of a content item that is skipped results in an exception because View(null) is abigious
So, what would be the correct approach here or is there any module in existence that does the job? I couldn't find one.
This is a quite complex task. There are several steps needed to achieve a proper skipping of display items:
Create the part correctly
There are a few pitfalls here as when coming to the task of adding a part view one might utilize Orchards date time editor in connection with the DateTime properties. But this brings a heck of a lot of additional issues to the table but these don't really relate to the question.
If someone is interested in how to use Orchards date time editor then i can post this code too, but for now it would only blow up the code unnecessarly.
So here we go, the part class...
public class ValidityPart : Orchard.ContentManagement.ContentPart<ValidityPartRecord>
{
// public
public System.DateTime? ValidFromUtc
{
get { return Retrieve(r => r.ValidFromUtc); }
set { Store(r => r.ValidFromUtc, value); }
}
...
public System.DateTime? ValidTillUtc
{
get { return Retrieve(r => r.ValidTillUtc); }
set { Store(r => r.ValidTillUtc, value); }
}
...
public bool IsContentItemValid()
{
var lUtcNow = System.DateTime.UtcNow;
return (ValidFromUtc == null || ValidFromUtc.Value <= lUtcNow) && (ValidTillUtc == null || ValidTillUtc.Value >= lUtcNow);
}
...
}
...and the record class...
public class ValidityPartRecord : Orchard.ContentManagement.Records.ContentPartRecord
{
// valid from value as UTC to use Orchard convention (see CommonPart table) and to be compatible with projections
// (date/time tokens work with UTC values, see https://github.com/OrchardCMS/Orchard/issues/6963 for a related issue)
public virtual System.DateTime? ValidFromUtc { get; set; }
// valid from value as UTC to use Orchard convention (see CommonPart table) and to be compatible with projections
// (date/time tokens work with UTC values, see https://github.com/OrchardCMS/Orchard/issues/6963 for a related issue)
public virtual System.DateTime? ValidTillUtc { get; set; }
}
Create a customized content query class
public class MyContentQuery : Orchard.ContentManagement.DefaultContentQuery
{
// public
public ContentQuery(Orchard.ContentManagement.IContentManager aContentManager,
Orchard.Data.ITransactionManager aTransactionManager,
Orchard.Caching.ICacheManager aCacheManager,
Orchard.Caching.ISignals aSignals,
Orchard.Data.IRepository<Orchard.ContentManagement.Records.ContentTypeRecord> aContentTypeRepository,
Orchard.IWorkContextAccessor aWorkContextAccessor)
: base(aContentManager, aTransactionManager, aCacheManager, aSignals, aContentTypeRepository)
{
mWorkContextAccessor = aWorkContextAccessor;
}
protected override void BeforeExecuteQuery(NHibernate.ICriteria aContentItemVersionCriteria)
{
base.BeforeExecuteQuery(aContentItemVersionCriteria);
// note:
// this method will be called each time a query for multiple items is going to be executed (e.g. content items of a container, layers, menus),
// this gives us the chance to add a validity criteria
var lWorkContext = mWorkContextAccessor.GetContext();
// exclude admin as content items should still be displayed / accessible when invalid as validity needs to be editable
if (lWorkContext == null || !Orchard.UI.Admin.AdminFilter.IsApplied(lWorkContext.HttpContext.Request.RequestContext))
{
var lUtcNow = System.DateTime.UtcNow;
// left outer join of ValidityPartRecord table as part is optional (not present on all content types)
var ValidityPartRecordCriteria = aContentItemVersionCriteria.CreateCriteria(
"ContentItemRecord.ValidityPartRecord", // string adopted from foreach loops in Orchard.ContentManagement.DefaultContentQuery.WithQueryHints()
NHibernate.SqlCommand.JoinType.LeftOuterJoin
);
// add validity criterion
ValidityPartRecordCriteria.Add(
NHibernate.Criterion.Restrictions.And(
NHibernate.Criterion.Restrictions.Or(
NHibernate.Criterion.Restrictions.IsNull("ValidFromUtc"),
NHibernate.Criterion.Restrictions.Le("ValidFromUtc", lUtcNow)
),
NHibernate.Criterion.Restrictions.Or(
NHibernate.Criterion.Restrictions.IsNull("ValidTillUtc"),
NHibernate.Criterion.Restrictions.Ge("ValidTillUtc", lUtcNow)
)
)
);
}
}
// private
Orchard.IWorkContextAccessor mWorkContextAccessor;
}
This essentially adds a left join of the validity part fields to the SQL query (content query) and extends the WHERE statement with the validity condition.
Please note that this step is only possible with the solution described the following issue: https://github.com/OrchardCMS/Orchard/issues/6978
Register the content query class
public class ContentModule : Autofac.Module
{
protected override void Load(Autofac.ContainerBuilder aBuilder)
{
aBuilder.RegisterType<MyContentQuery>().As<Orchard.ContentManagement.IContentQuery>().InstancePerDependency();
}
}
Create a customized content manager
public class ContentManager : Orchard.ContentManagement.DefaultContentManager
{
// public
public ContentManager(
Autofac.IComponentContext aContext,
Orchard.Data.IRepository<Orchard.ContentManagement.Records.ContentTypeRecord> aContentTypeRepository,
Orchard.Data.IRepository<Orchard.ContentManagement.Records.ContentItemRecord> aContentItemRepository,
Orchard.Data.IRepository<Orchard.ContentManagement.Records.ContentItemVersionRecord> aContentItemVersionRepository,
Orchard.ContentManagement.MetaData.IContentDefinitionManager aContentDefinitionManager,
Orchard.Caching.ICacheManager aCacheManager,
System.Func<Orchard.ContentManagement.IContentManagerSession> aContentManagerSession,
System.Lazy<Orchard.ContentManagement.IContentDisplay> aContentDisplay,
System.Lazy<Orchard.Data.ITransactionManager> aTransactionManager,
System.Lazy<System.Collections.Generic.IEnumerable<Orchard.ContentManagement.Handlers.IContentHandler>> aHandlers,
System.Lazy<System.Collections.Generic.IEnumerable<Orchard.ContentManagement.IIdentityResolverSelector>> aIdentityResolverSelectors,
System.Lazy<System.Collections.Generic.IEnumerable<Orchard.Data.Providers.ISqlStatementProvider>> aSqlStatementProviders,
Orchard.Environment.Configuration.ShellSettings aShellSettings,
Orchard.Caching.ISignals aSignals,
Orchard.IWorkContextAccessor aWorkContextAccessor)
: base(aContext, aContentTypeRepository, aContentItemRepository, aContentItemVersionRepository, aContentDefinitionManager, aCacheManager, aContentManagerSession,
aContentDisplay, aTransactionManager, aHandlers, aIdentityResolverSelectors, aSqlStatementProviders, aShellSettings, aSignals)
{
mWorkContextAccessor = aWorkContextAccessor;
}
public override ContentItem Get(int aId, Orchard.ContentManagement.VersionOptions aOptions, Orchard.ContentManagement.QueryHints aHints)
{
var lResult = base.Get(aId, aOptions, aHints);
if (lResult != null)
{
// note:
// the validity check is done here (after the query has been executed!) as changing base.GetManyImplementation() to
// apply the validity critera directly to the query (like in ContentQuery) will not work due to a second attempt to retrieve the
// content item from IRepository<> (see base.GetManyImplementation(), comment "check in memory") when the query
// returns no data (and the query should not return data when the validity critera is false)
//
// http://stackoverflow.com/q/37841249/3936440
var lWorkContext = mWorkContextAccessor.GetContext();
// exclude admin as content items should still be displayed / accessible when invalid as validity needs to be editable
if (lWorkContext == null || !Orchard.UI.Admin.AdminFilter.IsApplied(lWorkContext.HttpContext.Request.RequestContext))
{
var lValidityPart = lResult.As<ValidityPart>();
if (lValidityPart != null)
{
if (lValidityPart.IsContentItemValid())
{
// content item is valid
}
else
{
// content item is not valid, return null (adopted from base.Get())
lResult = null;
}
}
}
}
return lResult;
}
// private
Orchard.IWorkContextAccessor mWorkContextAccessor;
}
Steps 2-4 are needed when having content items whereas the content type has a Container and Containable part or even content items which are processed / displayed separately. Here you normally cannot customize the content query that is executed behind the scenes.
Steps 2-4 are not needed if you use the Projection module. But again, this brings a few other issues to the table as reported in this issue: https://github.com/OrchardCMS/Orchard/issues/6979

How to implement "Edit" method for complex model in asp.net MVC 3

In my mvc 3 web store app I have this model: Items, Products, Shipping, Users tables, where 1 Item contains 1 Product, 1 User, and 1 Shipping. I need to somehow create and edit items. With creating there are not many problems cause I can pass the "fields to fill" as parameters to Crete(Post) method. With editing there is one problem - Null Reference Exception occures. This is my code:
(controller):
I pass model (of type Item) to Edit Post method as a parameter and get product and shipping, that, I hope), I filled with values.
[HttpGet]
public ViewResult Edit(int itemId)
{
Item target = _itemsRepository.GetItem(itemId);
return View(target);
}
[HttpPost]
public ActionResult Edit(Item model)
{
Product p = model.Product;
Shipping s = model.Shipping;
// here goes some validation stuff
if (ModelState.IsValid)
{
_productRepository.UpdateProduct(p);
_shippingRepository.UpdateShipping(s);
return RedirectToAction("Content");
}
return View();
}
Strongly typed view (where I fill the form):
#model WebStore.WebStoreModels.Item
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Product</legend>
#Html.HiddenFor(i => i.ItemId)
<p>Name: </p>
<p>
#Html.EditorFor(i => i.Product.Name)
#Html.ValidationMessageFor(i => i.Product.Name)
</p>
// and so on
</fieldset>
<fieldset>
<legend>Shipping</legend>
<p>Cost: </p>
<p>
#Html.EditorFor(i => i.Shipping.Cost)
#Html.ValidationMessageFor(i => i.Shipping.Cost)
</p>
// so on
</fieldset>
<p>
<input type="submit" value="Save"/>
</p>
}
And when I fill the form and click "safe" button the NullReferenceException occures in the ProductRepository class in UpdateProduct() method:
public class ProductRepository : IProductRepository
{
private WebStoreDataContext _dataContext;
public ProductRepository(WebStoreDataContext dataContext)
{
_dataContext = dataContext;
}
public Product GetProduct(int productId)
{
return _dataContext.Products.SingleOrDefault(p => p.ProductId == productId);
}
public void UpdateProduct(Product p)
{
var dbProduct = GetProduct(p.ProductId);
dbProduct.Name = p.Name; // here the exception occures
dbProduct.Description = p.Description;
dbProduct.Price = p.Price;
dbProduct.Category = p.Category;
dbProduct.SubCategory = p.SubCategory;
_dataContext.SubmitChanges();
}
Seems like I can't use this assignment:
Product p = model.Product;
I also tried: (in view and then assign it to Product in Edit(Post) method)
TempData["product"] = #Model.Product;
And also: (in view)
#Html.Hidden("product", #Model.Product)
And pass it as parameters to Edit(Post) method:
[HttpGet]
public ViewResult Edit(Product p, Shipping s)
I think the problem is associated with model.
Any help would be great, Sorry for so much code)
You need to add hidden ProductId input inside of the form in your View:
#using (Html.BeginForm())
{
#Html.HiddenFor(i => i.Product.ProductId)
...
}
The reason you need to have this line is that when you submit your form model binder looks at every input (including hidden inputs) and binds them to the properties of your Item model that you pass on [HttpPost] action. Before, when you didn't have this line, only the following properties were populated:
Item.ItemId
Item.Product.Name
Item.Shipping.Cost
Your model didn't have information for the value of Item.Product.ProductId. This property is int, which means that it was turning to be equal 0 when form was submitted. Inside your _productRepository.UpdateProduct(p); method you are trying to get a product by Id and obviously it cannot find a product with id = 0, which is why this call was returning null resulting in a null reference exception on the next line.

Data binding to an enum on a command in Grails

I have a class:
class User {
Set<Foo> foos = []
}
where Foo is an enum:
class Foo { A, B, C, D}
I have a controller action with a parameter of type User
def someAction = {User user ->
// impl omitted
}
I've created a multi-select in a GSP
<g:select name="foos" multiple="true" from="${Foo.values()}"/>
But when I submit the form the selected values do not get bound to the foos property of the User command object. What am I doing wrong?
http://www.grails.org/TipsAndTricks
Enum usage
If you want to use a Enum with a "value" String attribute (a pretty common idiom) in a element, try this:
enum Rating {
G("G"),PG("PG"),PG13("PG-13"),R("R"),NC17("NC-17"),NR("Not Rated")
final String value
Rating(String value) { this.value = value }
String toString() { value }
String getKey() { name() }
}
Then add optionKey="key" to your tag. Credit: Gregg Bolinger
This isn't really going to be an answer, per se. But I can't post the details of this in a comment. I just created the following:
enum State {
OK,KS,FL,MA
}
class User {
Set<State> states = []
static constraints = {
}
}
<g:form controller="home" action="save">
<g:select name="states" multiple="true" from="${com.orm.fun.State.values()}"/>
<g:submitButton name="save" value="Save"/>
</g:form>
// controller action
def save = { User user ->
// I didn't do anything here except
// set a breakpoint for debugging
}
And this is what I get:
So I'm not entirely sure what is different between yours and mine, except the name of the enum. Can you see anything?

Resources