This is a bit embarrassing as I'm sure the answer is simple.
I'm using Entityframework and Code First techniques trying to build my first functional MVC form while following the excellent ASP.NET tutorials.
How do I display a string that includes Parent information to the table being queried. I wanted to include a parent value in the string being used for the dropdown list .... or should I be doing this the opposite way and selecting the parent and have the children show up as a result of the selection?
I thought it would be as simple as adding to the model since it is already talking to its parent. Intellisense is okay with it :-)
Model Class
public class SourceLocation
{
[Key]
public int SourceLocationID { get; set; }
public int SourceID { get; set; }
[Required]
[Display(Name = "Product Type")]
[StringLength(25)]
public string ProductType { get; set; }
[Required]
[Display(Name = "Source Location")]
[StringLength(50)]
public string SamplingLocation { get; set; }
[Display(Name = "Sampling Location Notes")]
public string LocationNotes { get; set; }
public string SourceProductType
{
get
{
return CementSources.SampleSource + " " + ProductType + " ex " + SamplingLocation;
}
}
public virtual CementSource CementSources { get; set; }
}
}
The Controller referencing SourceSampleType is configured thusly.
// GET: Specifications/Create
public ActionResult Create()
{
ViewBag.FieldID = new SelectList(db.Fields, "FieldID", "FieldName");
ViewBag.SourceLocationID = new SelectList(db.SourceLocations, "SourceLocationID", "SourceProductType");
ViewBag.SpecificationTypeID = new SelectList(db.SpecificationTypes, "SpecificationTypeID", "SpecificationTypeName");
return View();
}
When I try to create a new Specimen which is configured to show SourceSampleType, the error is:
There is already an open DataReader associated with this Command which must be closed first.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.InvalidOperationException: There is already an open DataReader associated with this Command which must be closed first.
Source Error:
Line 28: get
Line 29: {
Line 30: return CementSources.SampleSource + " " + ProductType + " ex " + SamplingLocation;
Line 31: }
Line 32: }
Am I not using this syntax correctly or is it something related to Eager/Lazy loading that I have yet to parse and understand?
The error occurs because you iterating through the results of SourceLocation but in each iteration, you executing another query to get the value of its CementSources property.
You need to remove the SourceProductType property from the model, and use Include() in the query to include CementSources
ViewBag.SourceLocationID = db.SourceLocations
.Include(x => x.CementSources)
.Select(x => new SelectListItem
{
Value = x.SourceLocationID.ToString(),
Text = string.Format("{0} {1} ex {2}", x.CementSources.SampleSource, x.ProductType, x.SamplingLocation)
});
Related
I am getting this error "Self referencing loop detected" while serializing using 'Json.NET'
I have a Book model
public class Book
{
public Book()
{
BookPersonMap = new List<BookPersonMap>();
}
public int BookId { get; set; }
public virtual ICollection<BookPersonMap> BookPersonMap { get; private set; }
(And many other virtual Icollections)
}
And this is the BookPerson Mapping class:
public class BookPersonMap
{
public int BookId { get; set; }
public string PersonName { get; set; }
public int PersonTypeId { get; set; }
public virtual Book Book { get; set; } // Foreign keys
public virtual PersonType PersonType { get; set; }
}
When I try to Serialize the Book object it throws:
"Self referencing loop detected for property 'Book' with type 'System.Data.Entity.DynamicProxies.Book_57F0FA206568374DD5A4CFF53C3B41CFDDC52DBBBA18007A896 08A96E7A783F8'. Path 'BookPersonMap[0]'."
I have tried the things suggested in some of the similar posts
Example:
PreserveReferencesHandling = PreserveReferencesHandling.Objects in Serializer settings returned a string with length 3 million!
ReferenceLoopHandling = ReferenceLoopHandling.Ignore in Serializer settings :
"An exception of type 'System.OutOfMemoryException' occurred in Newtonsoft.Json.dll but was not handled in user code"
^ Same luck with "ReferenceLoopHandling.Serialize"
MaxDepth = 1 : Infinite loop again.
Putting [JsonIgnore] on the virtual properties is working but it is a tedious task (because of numerous FK references) and not efficent, since if I miss one property and it will throw exception.
What is missing from above Json settings for them be not working?
services.AddMvc().AddJsonOptions(opt => {
opt.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
});
I have found the best way to solve this type of error is to flatten your model using a view model.
Put a break point on your object before it is serialized and start drilling into the child properties. You will probably find that you can go on indefinitely.
This is what the serializer is choking on.
Create a Constructor for your controller and put on it this line of code :
db.Configuration.ProxyCreationEnabled = false;
//db is the instance of the context.
For asp.net mvc 5 use this
Add the code below to your Application_Start method inside globax.asax file or startup file.
protected void Application_Start()
{
..
GlobalConfiguration.Configuration.Formatters.JsonFormatter
.SerializerSettings
.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
}
Disable lazy loading and
ensure your controller does not return
Json(..obj)
rather it should return
Ok(..obj)
I have a table where I store all the different code/value keywords that I need in my app:
public class Keyword
{
public int id { get; set;}
public string name { get; set; }
public string valuecode { get; set; }
public string valuename { get; set; }
}
Then I use Keyword to store records like these
name valuecode valuename
.DealState 1 Draft
.DealState 2 Final
.DealState 3 Cancelled
.DealType NEW New Business
.DealType RNW Renewal
.DealType WFA Waiting for approval
Then in other models I have fields that are filled using these keywords. For example,
public class Deal
{
....
public string state { get; set; }
public string type { get; set; }
....
}
I have managed to have the fields filled with "valuecode" while displaying "valuename" in Create and Edit views (I use DropDownList with a SelectList built in the controller), but I cannot find a way to display valuename instead of valuecode in Index and Details views.
I'm trying to pass the same SelectList in the ViewBag for Index, but then I do not know which syntax to use in order to replace the "state" code with the state "description" for each record returned.
Any hint?
PS: I'm quite new to .net and mvc, usually work with RoR and ActiveRecord...
EDIT
In my KeywordController I have a method
public SelectList selectKeywordValues(string kwname, object selectedKeyword = null)
{
var keywordsQuery = from d in db.Keywords
where d.name == kwname
orderby d.valuename
select d;
SelectList kwlist = new SelectList(keywordsQuery, "valuecode", "valuename", selectedKeyword);
return kwlist;
}
Then in my DealController i have the index method
public ActionResult Index()
{
var kw = new KeywordController();
ViewBag.state = kw.selectKeywordValues(".DealState");
return View(db.Deals.ToList());
}
SOLVED
In DealController the index method is the following
public ActionResult Index()
{
var kw = new KeywordController();
SelectList states = kw.selectKeywordValues(".DealState");
SelectList types = kw.selectKeywordValues(".DealType");
foreach (var item in db.Deals.ToList())
{
SelectListItem mystate = states.Where(row => row.Value == item.state).ElementAt(0);
SelectListItem mytype = types.Where(row => row.Value == item.type).ElementAt(0);
item.state = mystate.Text;
item.type = mytype.Text;
}
return View(db.Deals.ToList());
}
Now the db.Deals.ToList() is filled with descriptions and not with codes.
You can define a view model called DealViewModel that contains DealState and DealType properties. Then populate the DealViewModel with joins in LINQ before passing to the views that reference the view model.
Another approach is to use enums in EF5.
Ok, I've read Phil Haack's article on binding to a list and I've got that working fine on one view. But what I'm stuck when doing it off a master record.
I've got a really simple form for this object
public class Master
{
public int ID { get; set; }
public string MasterTitle { get; set; }
public virtual IList<Detail> Details { get; set; }
}
public class Detail
{
public int ID { get; set; }
public string DetailName { get; set; }
public virtual Master Master { get; set; }
}
The form collection comes back with the expected prefixes:
[0] ""
[1] "ID"
[2] "MasterTitle"
[3] "Details[0].ID"
[4] "Details[0]"
[5] "Details"
[6] "Details[0].DetailName"
[7] "Details[1].ID"
[8] "Details[1]"
[9] "Details[1].DetailName" string
And the Controller.UpdateModel(master) binds all the properties correctly. But when I call dbContext.SaveChanges it issues the follow sql from sql profiler (psuedo code)
update detail1 set masterID = null
update detail2 set masterID = null
update master set masterName = 'newname'
insert detail1 ...
insert detail2 ...
I've got a work around that works but it's pretty hackish and I'm currently not matching up the keys so it's dependent on everything coming back in the right order. Plus I've got to include all the fields that I want updated.
public ActionResult Edit(FormCollection collection)
{
try
{
using (var ctx = new PlayContext())
{
var id = int.Parse(collection["ID"]);
Master master = ctx.Master.Find(id);
UpdateModel(master, new [] {"MasterTitle"});
for (int i = 0; i < master.details.Count; i++)
{
UpdateModel(master.details[i], "Details[" + i + "]", new[] { "DetailName" });
}
ctx.SaveChanges();
return View(master);
}
}
catch (Exception e)
{
ModelState.AddModelError("", e);
}
return View();
}
I've got a feeling that UpdateModel is somehow removing and re-adding the children.
Has anyone else got this to work? Of course, I could throw in the towel and parse the indexed field names myself, but I'm so close!
It should work - I haven't had any problems with similar code in MVC2.
I'm worried about this line though:
[5] "Details"
What's it sending back in details? I expect this could be causing the problem - Not entirely sure how the model binder works in MVC 3, but I'd expect this line would cause it to set the Details collection to NULL.
You shouldn't have to rely on the fields returning in a specific order - the binder would be designed to handle them in any order.
EDIT: upgraded this question to MVC 2.0
With asp.net MVC 2.0 is there an existing method of creating Validation Summary that makes sense for models containing collections? If not I can create my own validation summary
Example Model:
public class GroupDetailsViewModel
{
public string GroupName { get; set; }
public int NumberOfPeople { get; set; }
public List<Person> People{ get; set; }
}
public class Person
{
[Required(ErrorMessage = "Please enter your Email Address")]
[RegularExpression(#"^([a-zA-Z0-9_\-\.]+)#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$", ErrorMessage = "Please enter a valid Email Address")]
public string EmailAddress { get; set; }
[Required(ErrorMessage = "Please enter your Phone Number")]
public string Phone { get; set; }
[Required(ErrorMessage = "Please enter your First Name")]
public string FirstName { get; set; }
[Required(ErrorMessage = "Please enter your Last Name")]
public string LastName { get; set; }
}
The existing summary <%=Html.ValidationSummary %> if nothing is entered looks like this.
The following error(s) must be corrected before proceeding to the next step
* Please enter your Email Address
* Please enter your Phone Number
* Please enter your First Name
* Please enter your Last Name
* Please enter your Email Address
* Please enter your Phone Number
* Please enter your First Name
* Please enter your Last Name
The design calls for headings to be inserted like this:
The following error(s) must be corrected before proceeding to the next step
Person 1
* Please enter your Email Address
* Please enter your Phone Number
* Please enter your First Name
* Please enter your Last Name
Person 2
* Please enter your Email Address
* Please enter your Phone Number
* Please enter your First Name
* Please enter your Last Name
Answer Based on Pharcyde's answer.
public static MvcHtmlString NestedValidationSummary(this HtmlHelper helper)
{
if (helper.ViewData.ModelState.IsValid)
return MvcHtmlString.Empty;
// create datastructure to group error messages under a given key (blank key is for general errors)
var errors = new Dictionary<string,List<string>>();
foreach (KeyValuePair<string, ModelState> keyPair in helper.ViewData.ModelState)
{
foreach (ModelError error in keyPair.Value.Errors)
{
//determine the 'key' for the group in which this error belongs
var key = keyPair.Key.Split(']')[0];
if (key.Contains("People["))
key = "Person " + key.Split('[')[1];
else
key = string.Empty;
if(!errors.ContainsKey(key))
errors.Add(key,new List<string>());
//now add message using error.ErrorMessage property
errors[key].Add(error.ErrorMessage);
}
}
// generate the HTML
var ul = new TagBuilder("ul");
foreach (KeyValuePair<string, List<string>> errorPair in errors.OrderBy(p=>p.Key))
{
var li = new TagBuilder("li");
if(!string.IsNullOrEmpty(errorPair.Key))
li.InnerHtml += string.Format("<p class=\"no-bottom-margin\"><strong>{0}</strong></p>",errorPair.Key);
var innerUl = new TagBuilder("ul");
foreach (var message in errorPair.Value)
{
var innerLi = new TagBuilder("li");
innerLi.InnerHtml = message;
innerUl.InnerHtml += innerLi.ToString(TagRenderMode.Normal);
}
li.InnerHtml += innerUl.ToString(TagRenderMode.Normal);
ul.InnerHtml += li.ToString(TagRenderMode.Normal);
}
return MvcHtmlString.Create(ul.ToString(TagRenderMode.Normal));
}
You are going to have to extend the HtmlHelper methods and roll your own. Heres the bit of code that is important for your situation where you need a group by:
//HtmlHelper being extended
if(helper.ViewData.ModelState.IsValid)
{
foreach(KeyValuePair<string,ModelState> keyPair in helper.ViewData.ModelState)
{
//add division for group by here using keyPair.Key property (would be named "Person" in your case).
foreach(ModelError error in keyPair.Value.Errors)
{
//now add message using error.ErrorMessage property
}
}
}
There appears to be something of a hole in the way DataAnnotations works in that a user entering in some text into a field that will go into an int will never reach the DataAnnotations code. It kicks off a model binding error and displays the error to the user "The value 'a' is not valid for the XXXX field."
Anyway, it's all very nice that it automatically handles this situation, but I actually want to display an error message indicating the problem eg. "The value 'a' is not numeric. Please enter in a numeric value for the XXXX field".
I have tried the solutions set out How to replace the default ModelState error message in Asp.net MVC 2? and ASP.NET MVC - Custom validation message for value types, but I can't get them to work.
It appears that my resource file is not being read at all, since here (http://msdn.microsoft.com/en-us/library/system.web.mvc.defaultmodelbinder.resourceclasskey.aspx) it states "If the property is set to an invalid class key (such as a resource file that does not exist), MVC throws an exception." and even if I change the line to DefaultModelBinder.ResourceClassKey = "asdfasdhfk" there is no exception.
Anyone have any ideas?
EDIT: Here is some code. All of it is working minus my Messages.resx file's messages are not being used. The code for Messages.resx is auto generated so I won't include it.
So entering "a" into ProcessOrder results in a generic message rather than what I have entered into Messages.resx for PropertyValueInvalid (and InvalidPropertyValue for good measure).
Application_Start method
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
ModelBinders.Binders.DefaultBinder = new Microsoft.Web.Mvc.DataAnnotations.DataAnnotationsModelBinder(); //set dataanooations to be used
DefaultModelBinder.ResourceClassKey = "Messages"; //set data annotations to look in messages.resx for the default messages
ValidationExtensions.ResourceClassKey = "Messages";
}
Entity Class
[MetadataType(typeof(GLMetaData))]
public partial class GL
{
}
public class GLMetaData
{
public int TransRefId { get; set; }
[DisplayName("Process Order")]
public int? ProcessOrder { get; set; }
[DisplayName("Trans Type")]
[StringLength(50)]
public string TransType { get; set; }
[StringLength(100)]
public string Description { get; set; }
[DisplayName("GL Code")]
[StringLength(20)]
public string GLCode { get; set; }
[DisplayName("Agents Credit No")]
[StringLength(50)]
public string AgentsCreditNo { get; set; }
[Required]
public bool Active { get; set; }
}
Controller Action:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(GL glToBeUpdated)
{
try
{
if (!ModelState.IsValid)
return View(glToBeUpdated);
//set auto properties
glToBeUpdated.UpdateDate = DateTime.Now;
glToBeUpdated.UpdateUser = this.CurrentUser;
glDataLayer.update(glToBeUpdated);
glDataLayer.submitChanges();
return RedirectToAction("Index");
}
catch
{
glDataLayer.abortChanges();
throw;
}
}
What I did to combat a similar issue was to clear the model state, validate against ModelState["XXXX"].Value.AttemptedValue instead of against the nulled value caused by an trying to put an invalid value into the Model's property, populating the error messages and resetting the Model values.
That way I can have the error messages I want and if necessary offer more than one ("a value is required" or "the value must be numeric").
I have battled this for most of the day on MVC4 RC. No matter what i set
DefaultModelBinder.ResourceClassKey
to it never seemed to work. It also never threw an exception when I assigned junk.
This is what I was using to assign the value (to no avail):
DefaultModelBinder.ResourceClassKey = typeof(App_GlobalResources.ValidationMessages).Name;
In the end I decided to tackle this error message on the client side and override the data attribute that jQuery uses to display the message.
#Html.TextBoxFor(m => m.Amount, new Dictionary<string,object>(){{"data-val-number","Invalid Number"}})
this is working how I need it to.
Ironically this works too:
#Html.TextBoxFor(m => m.Amount, new Dictionary<string, object>() {{ "data-val-number", HttpContext.GetGlobalResourceObject("ValidationMessages", "PropertyValueInvalid") } })
Here I have taken Contact number field as string but with Range Attribute so can provide numeric validatioin to use if from your Resource file .
[Required(ErrorMessageResourceType = typeof(Global), ErrorMessageResourceName = "ContactNumberRequired")]
[Range(0, int.MaxValue, ErrorMessageResourceType = typeof(Global), ErrorMessageResourceName = "ValidContactNumber")]
[Display(Name = "Contact Number")]
public string ContactNumber { get; set; }
So now here provided ErrorMessageResourceName as key . You can customize and use it also in Multi Language