Client side validation for my dropdown populated with enum values - asp.net-mvc

I have a view where I use a dropdown list with enum:
public enum MaterialWorthEnumViewModel
{
[Display(Name = "")] Undefined,
[Display(Name = "< 1.000€")] LessThan1000,
[Display(Name = "1.000€ < 10.000€")] Between1000And10000,
[Display(Name = "10.000€ < 100.000€")] Between10000And100000,
[Display(Name = "100.000€ < 25.000.000€")] Between100000And25000000,
[Display(Name = "> 25.000.000€")] GreaterThan250000000,
}
I use a view model with this view:
public class MaterialEditNewViewModel
{
public int RequestID { get; set; }
...
[EnumRequired]
public MaterialWorthEnumViewModel MaterialWorth { get; set; }
}
As you can see above, I used a custom validation [EnumRequired] I grab the code from a blog online.
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public class EnumRequiredAttribute : RequiredAttribute
{
private const string UNDEFINED_VALUE = "Undefined";
public string UndefinedValue { get; set; }
public EnumRequiredAttribute() : this(UNDEFINED_VALUE)
{ }
public EnumRequiredAttribute(string undefinedValue) : base()
{
if (String.IsNullOrWhiteSpace(undefinedValue))
{
throw new ArgumentNullException("undefinedValue");
}
UndefinedValue = undefinedValue;
}
public override bool IsValid(object value)
{
if (value == null)
{
return false;
}
var undefined = Enum.Parse(value.GetType(), UndefinedValue);
return !Enum.Equals(value, undefined);
}
}
Below is for the client side validation
public class ModelClientValidationEnumRequiredRule : ModelClientValidationRule
{
public ModelClientValidationEnumRequiredRule(string errorMessage, string undefinedValue)
{
base.ErrorMessage = errorMessage;
base.ValidationType = "enumrequired";
base.ValidationParameters.Add("undefinedvalue", undefinedValue);
}
}
public class EnumRequiredAttributeAdapter : DataAnnotationsModelValidator<EnumRequiredAttribute>
{
public EnumRequiredAttributeAdapter(ModelMetadata metadata, ControllerContext context, EnumRequiredAttribute attribute)
: base(metadata, context, attribute)
{ }
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
return new ModelClientValidationEnumRequiredRule[]
{
new ModelClientValidationEnumRequiredRule(base.ErrorMessage, Attribute.UndefinedValue)
};
}
}
Below is the javascript for the client side validation
Sys.Mvc.ValidatorRegistry.validators.enumrequired = function (rule) {
var undefinedValue = rule.ValidationParameters.undefinedvalue;
return function (value, context) {
return value != undefinedValue;
}
}
I also updated my GLobal.asax file:
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(EnumRequiredAttribute), typeof(EnumRequiredAttributeAdapter));
The validation works pretty well on the server side but the client side validation is never triggered. So when I didn't choose any value on my view for my dropdown enum, I reach the action in the controller and then the server side validation occured and I go back to the view. I concluded that the client side validation didn't occurred.
Does someone can help me doing valid client side validation for this dropdown enum ?
Thanks. I'm a bit lost.

I don't see any relationship between your EnumRequiredAttribute and the other 2 classes. If you are using ASP.NET MVC 3 you need to associate your custom validation attribute with the adapter. This could be done in Application_Start:
DataAnnotationsModelValidatorProvider.RegisterAdapter(
typeof(EnumRequiredAttribute),
typeof(EnumRequiredAttributeAdapter)
);
Also on your client side you have shown some js code that relies on Microsoft*.js libraries. Those are now obsolete and should no longer be used. The default standard in ASP.NET MVC 3 for client side validation is the jquery.validate plugin.
So let's take an example.
Model:
public class MyViewModel
{
[EnumRequired]
public MaterialWorthEnumViewModel MaterialWorth { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new MyViewModel());
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
View (Index.cshtml):
#model MyViewModel
<script src="#Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/enumrequiredadapter.js")" type="text/javascript"></script>
#using (Html.BeginForm())
{
#Html.LabelFor(x => x.MaterialWorth)
#Html.EditorFor(x => x.MaterialWorth)
#Html.ValidationMessageFor(x => x.MaterialWorth)
<button type="submit">OK</button>
}
and finally the enumrequiredadapter.js adapter:
(function ($) {
$.validator.unobtrusive.adapters.add('enumrequired', ['undefinedvalue'], function (options) {
options.rules['enumrequired'] = options.params;
if (options.message != null) {
options.messages['enumrequired'] = options.message;
}
});
$.validator.addMethod('enumrequired', function (value, element, params) {
return value != params.undefinedvalue;
});
})(jQuery);
Also don't forget to remove all traces of Microsoft*.js script references from your site. And that's pretty much it.

Related

Custom Validation for nested model in .net core

I am trying to validate a nested model using custom validation. But the problem is AttributeAdapterBase.AddValidation function is never called on nested model. However it works well with simple class property
Custom required validation attribute:
public interface IId
{
long Id { get; set; }
}
public class Select2RequiredAttribute : RequiredAttribute
{
public Select2RequiredAttribute(string errorMessage = "") : base()
{
ErrorMessage = errorMessage;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Type t = value.GetType();
if (typeof(IId).IsAssignableFrom(t))
{
if ((value as IId).Id == 0)
{
return new ValidationResult(ErrorMessage);
}
}
else
{
return new ValidationResult(ErrorMessage);
}
return ValidationResult.Success;
}
}
Attribute adapter base:
public class Select2RequiredAttributeAdapter : AttributeAdapterBase<Select2RequiredAttribute>
{
public Select2RequiredAttributeAdapter(Select2RequiredAttribute attribute, IStringLocalizer stringLocalizer) : base(attribute, stringLocalizer)
{
}
public override void AddValidation(ClientModelValidationContext context)
{
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-select2-required", GetErrorMessage(context));
}
public override string GetErrorMessage(ModelValidationContextBase validationContext)
{
return Attribute.ErrorMessage ?? GetErrorMessage(validationContext.ModelMetadata, validationContext.ModelMetadata.GetDisplayName());
}
}
Adapter provider:
public class Select2RequiredAdapterProvider : IValidationAttributeAdapterProvider
{
private readonly IValidationAttributeAdapterProvider _baseProvider = new ValidationAttributeAdapterProvider();
public IAttributeAdapter GetAttributeAdapter(ValidationAttribute attribute, IStringLocalizer stringLocalizer)
{
if (attribute is Select2RequiredAttribute)
{
return new Select2RequiredAttributeAdapter(attribute as Select2RequiredAttribute, stringLocalizer);
}
else
{
return _baseProvider.GetAttributeAdapter(attribute, stringLocalizer);
}
}
}
Startup.cs:
services.AddSingleton<IValidationAttributeAdapterProvider, Select2RequiredAdapterProvider>();
Model classes:
public interface IBaseBriefViewModel : IId
{
string Name { get; set; }
}
public class BaseBriefViewModel : IBaseBriefViewModel
{
public virtual long Id { get; set; }
public string Name { get; set; }
}
public class UserViewModel
{
public long Id { get; set; }
public string Name { get; set; }
[Select2Required("Branch is required.")]
public BaseBriefViewModel Branch { get; set; }
}
Branch select 2 partial view:
#model DataLibrary.ViewModels.BriefViewModels.BaseBriefViewModel
#{
var elementId = ViewData["ElementId"] != null && !string.IsNullOrEmpty(ViewData["ElementId"].ToString()) ? ViewData["ElementId"].ToString() : "branch-id";
}
<div class="form-group">
<label>Branch: <span class="text-danger"></span></label>
<div class="row">
<div class="#select2Class">
#Html.DropDownListFor(model => model.Id, new List<SelectListItem>() {
new SelectListItem()
{
Value = (Model!=null&&Model.Id>0)?Model.Id.ToString():"",
Text = (Model!=null&&Model.Id>0)?Model.Name:"",
Selected = (Model!=null&&Model.Id>0)?true:false,
}}, new { #id = elementId, #class = "form-control disable-field"})
#Html.ValidationMessageFor(model => model.Id, "", new { #class = "text-danger" })
</div>
</div>
</div>
<script>
$(function () {
var id = "#" + "#elementId";
var url = '/Branch/GetBranchsForSelect2';
var dataArray = function (params) {
params.page = params.page || 1;
return {
prefix: params.term,
pageSize: pageSize,
pageNumber: params.page,
};
};
Select2AutoCompleteAjax(id, url, dataArray, pageSize, "---Branch---");
});
</script>
All this code works well for server side. But for better user experience I want to show error before submitting form. How can I achieve this? I want to use this BaseBriefViewModel for a lot of Select2 in the project. So hard coding a static error message is not a good idea. What I really want to do is pass a error message from parent object. Like Branch is required in this specific case. Maybe in some other class I might pass Product is required
Any direction will be appreciated
At the moment this is not supported - but support is in planned. See dotnet github issue:
https://github.com/dotnet/runtime/issues/36093

Polymorphic model binding / Complex Models

I have problem in model binding. When I submit form it returns me id=0 and device is null? and how to solve it. My goal is to add new device, and choose device type from view by selector. if user selects smartphone it has to add fields for smartphone. I don't want to save device type in base class as Kind variable. Thanks in advance(sorry for english)
controller->
public IActionResult Index()
{
MainCont mainCont = new MainCont();
return View(mainCont);
}
index.cshtml ->
#model MainCont
#{
ViewData["Title"] = "Home Page";
}
<form action="home/create" method="post">
#Html.Partial("example",Model.Device)
<button type="submit">გაგზავნა</button>
</form>
example.cshtml ->
#model SmartPhone
#Html.TextBoxFor(model => model.imei)
#Html.TextBoxFor(model => model.screensize)
Device Model ->
public abstract class Device : Object
{
}
LaptopModel ->
public class Laptop : Device
{
public string CPU { get; set; }
public string GPu { get; set; }
}
MainCont ->
public class MainCont
{
public int Id{ get; set; }
public Device Device { get; set; }
}
SmartphoneModel ->
public class SmartPhone : Device
{
public string screensize { get; set; }
public string imei { get; set; }
}
model binder ->
using Bind.Models;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Bind
{
public class DeviceModelBinder : IModelBinder
{
private Dictionary<Type, (ModelMetadata, IModelBinder)> binders;
public DeviceModelBinder(Dictionary<Type, (ModelMetadata, IModelBinder)> binders)
{
this.binders = binders;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
IModelBinder modelBinder;
ModelMetadata modelMetadata;
if (bindingContext.ModelType == typeof(Laptop))
{
(modelMetadata, modelBinder) = binders[typeof(Laptop)];
}
else if (bindingContext.ModelType == typeof(SmartPhone))
{
(modelMetadata, modelBinder) = binders[typeof(SmartPhone)];
}
else
{
bindingContext.Result = ModelBindingResult.Failed();
return;
}
var newBindingContext = DefaultModelBindingContext.CreateBindingContext(
bindingContext.ActionContext,
bindingContext.ValueProvider,
modelMetadata,
bindingInfo: null,
bindingContext.ModelName);
await modelBinder.BindModelAsync(newBindingContext);
bindingContext.Result = newBindingContext.Result;
if (newBindingContext.Result.IsModelSet)
{
// Setting the ValidationState ensures properties on derived types are correctly
bindingContext.ValidationState[newBindingContext.Result] = new ValidationStateEntry
{
Metadata = modelMetadata,
};
}
}
}
}
binderprovider ->
using Bind.Models;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Bind
{
public class DeviceModelBinderProvider: IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType != typeof(Device))
{
return null;
}
var subclasses = new[] { typeof(Laptop), typeof(SmartPhone), };
var binders = new Dictionary<Type, (ModelMetadata, IModelBinder)>();
foreach (var type in subclasses)
{
var modelMetadata = context.MetadataProvider.GetMetadataForType(type);
binders[type] = (modelMetadata, context.CreateBinder(modelMetadata));
}
return new DeviceModelBinder(binders);
}
}
}
Here is a demo:
Index.cshtml(when select SmartPhone,use example.cshtml,when select Laptop,use example1.cshtml):
#model MainCont
#{
ViewData["Title"] = "Home Page";
}
<form asp-action="create" asp-controller="home" method="post">
<select id="select" name="select">
<option value="SmartPhone">SmartPhone </option>
<option value="Laptop">Laptop </option>
</select>
<div id="sample"></div>
<button type="submit">გაგზავნა</button>
</form>
#section scripts{
<script>
$(function () {
GetPartialView();
})
$("#select").change(function () {
GetPartialView();
})
function GetPartialView() {
$.ajax({
url: "/Test1/ReturnExample",
type: "POST",
data: {
select: $("#select").val()
},
success: function (data) {
$('#sample').html(data);
},
error: function (reponse) {
alert("error : " + reponse);
}
});
}
</script>
}
example.cshtml:
#model SmartPhone
#Html.TextBoxFor(model => model.imei)
#Html.TextBoxFor(model => model.screensize)
example1.cshtml:
#model Laptop
#Html.TextBoxFor(model => model.CPU)
#Html.TextBoxFor(model => model.GPu)
Controller:
public IActionResult Index()
{
return View(new MainCont());
}
public IActionResult ReturnExample(string select)
{
if (select == "SmartPhone")
{
return PartialView("~/Views/Test1/example.cshtml", new SmartPhone());
}
else {
return PartialView("~/Views/Test1/example1.cshtml", new Laptop());
}
}
Create Action in Home Controller:
[HttpPost]
public IActionResult Create([ModelBinder(typeof(DataBinder))]MainCont mainCont) {
return Ok();
}
DataBinder:
public class DataBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var model1 = new MainCont();
var select = bindingContext.ValueProvider.GetValue("select").FirstValue;
if (select == "SmartPhone")
{
var model2 = new SmartPhone();
model2.screensize = bindingContext.ValueProvider.GetValue("screensize").FirstValue;
model2.imei = bindingContext.ValueProvider.GetValue("imei").FirstValue;
model1.Device = model2;
}
else if (select == "Laptop")
{
var model2 = new Laptop();
model2.CPU = bindingContext.ValueProvider.GetValue("CPU").FirstValue;
model2.GPu = bindingContext.ValueProvider.GetValue("GPu").FirstValue;
model1.Device = model2;
}
bindingContext.Result = ModelBindingResult.Success(model1);
return Task.CompletedTask;
}
}
result:

How to keep track of user's log-in/out in mvc

This question may sound trivial, but to me is complicated. I have created a table that stores user's log-in/out date and time. So in my MVC site, I need to be able to insert a new row when the user logs-in and update the row when user clicks on x button or navigates away, etc.
I did a bit of research on "void Session_Start(object sender, EventArgs e)" and thought that might be my solution. However, when I run my site in debug mode in VS, even when I log-out and log-in as another user to the site, the method doesnt get called. Am i missing something, or Session_Start is used for a different purpose?
Any help would be hugely appreciated.
Example:
//Model
{
public class LoginHistory
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
public DateTime LoginTime { get; set; }
public DateTime? LogoutTime { get; set; }
[StringLength(128)]
public string UsertId { get; set; }
}
//Controller
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
//............
switch (result)
{
case SignInStatus.Success:
LoginTime(model.UserName);
return RedirectToLocal(returnUrl);
//...........
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
//...............................
LogOutTime(User.Identity.GetUserId());
//...............................
return RedirectToAction("Index", "Home");
}
public void LoginTime(string userName)
{
using (var db = new Applicationdbcontext())
{
var user = db.user.Find(u => u.UserName == userName);
var model = new LoginHistory
{
UserId = user.Id,
LoginTime = DateTime.UtcNow
LogoutTime = null,
};
db.loginhistory.Add(model);
db.SaveChanges();
}
public void LogOutTime(string userId)
{
using (var db = new Applicationdbcontext())
{
var model = db.loginhistory.Where(u => u.Uid == userId).OrderByDescending(u => u.Id).First();
model.LogoutTime = DateTime.UtcNow;
_login.SaveChanges();
}
}
<script type='text/javascript'>
var inFormOrLink;
$('a').on('click', function() { inFormOrLink = true; });
$('form').on('submit', function() { inFormOrLink = true; });
$(window).on("beforeunload", function() {
if(!inFormOrLink){
document.getElementById('logoutForm').submit();
}
})
add this script in your layout page.
I always follow this example. Hopefully it's help for you.

Bundling with application cache in MVC4

I am working with an MVC project. And I use the application cache to store my whole page to the cache so that the application is still available even offline.
This is my Offline Controller
public class OfflineController : Controller
{
//
// GET: /Offline/
public ActionResult Index()
{
var manifestResult = new ManifestResult("1.0")
{
CacheResources = new List<string>()
{
Url.Action("Index", "Home"),
BundleTable.Bundles.ResolveBundleUrl("~/Content/css", true),
BundleTable.Bundles.ResolveBundleUrl("~/bundles/modernizr", true),
BundleTable.Bundles.ResolveBundleUrl("~/bundles/jquery",true),
BundleTable.Bundles.ResolveBundleUrl("~/bundles/jqueryui", true),
BundleTable.Bundles.ResolveBundleUrl("~/bundles/jqueryval",true),
BundleTable.Bundles.ResolveBundleUrl("~/bundles/modernizr",true),
BundleTable.Bundles.ResolveBundleUrl("~/Content/css",true),
BundleTable.Bundles.ResolveBundleUrl("~/Content/themes/base/css")
},
NetworkResources = new string[] { "*" },
FallbackResources = { { "Images/offline.jpg", "Images/offline.jpg" } }
};
return manifestResult;
}
}
My Home Controller is this
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application.";
return View();
}
public ActionResult About()
{
ViewBag.Message = "Your app description page.";
return View();
}
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
}
This is my class that generate the manifest file
public class ManifestResult : FileResult
{
public ManifestResult(string version)
: base("text/cache-manifest")
{
Version = version;
CacheResources = new List<string>();
NetworkResources = new List<string>();
FallbackResources = new Dictionary<string, string>();
}
public string Version { get; set; }
public IEnumerable<string> CacheResources { get; set; }
public IEnumerable<string> NetworkResources { get; set; }
public Dictionary<string, string> FallbackResources { get; set; }
protected override void WriteFile(HttpResponseBase response)
{
WriteManifestHeader(response);
WriteCacheResources(response);
WriteNetwork(response);
WriteFallback(response);
}
private void WriteManifestHeader(HttpResponseBase response)
{
response.Output.WriteLine("CACHE MANIFEST");
response.Output.WriteLine("#V" + Version ?? string.Empty);
}
private void WriteCacheResources(HttpResponseBase response)
{
response.Output.WriteLine("CACHE:");
foreach (var cacheResource in CacheResources)
response.Output.WriteLine(cacheResource);
}
private void WriteNetwork(HttpResponseBase response)
{
response.Output.WriteLine();
response.Output.WriteLine("NETWORK:");
foreach (var networkResource in NetworkResources)
response.Output.WriteLine(networkResource);
}
private void WriteFallback(HttpResponseBase response)
{
response.Output.WriteLine();
response.Output.WriteLine("FALLBACK:");
foreach (var fallbackResource in FallbackResources)
response.Output.WriteLine(fallbackResource.Key + " " + fallbackResource.Value);
}
}
Here is the error that I encounter
Before I answer, for those of you that just landed here, this is the implementation/article being referenced:
http://www.infoq.com/articles/Offline-Web-Apps
I was able to get this working just fine. One difference between your implementation and mine is that you are listing your manifest as such in the HTML tag:
<html manifest="/Offline">
.. or so I'm guessing.. you didn't post your HTML. I am not sure that HTML tag will be properly interpreted. Here is what I am using and works fine:
<html manifest="/Mobile/Manifest">
My guess is that "Application Cache Error event: Manifest fetch failed (-1)" is the equivalent of a 404.
Hope this helps... I see it's been quite a while since you posted.
I encountered the same issue of the css and script resources not loading in offline mode, when using the same infoq blog post as an example. I resolved the issue by adding the script and css resources using Scripts.Url instead of BundleTable.Bundles.ResolveBundleUrl.
My solution is based on a blog post by Eoin Clayton.
Using the Offline controller code you posted as a base, it may work if you modify it to look as follows:
public class OfflineController : Controller
{
//
// GET: /Offline/
public ActionResult Index()
{
var manifestResult = new ManifestResult("1.0")
{
CacheResources = new List<string>()
{
Url.Action("Index", "Home"),
Scripts.Url("~/bundles/jquery").ToString(),
Scripts.Url("~/bundles/modernizr").ToString(),
Scripts.Url("~/bundles/bootstrap").ToString(),
Scripts.Url("~/Content/css").ToString()
},
NetworkResources = new string[] { "*" },
FallbackResources = { { "Images/offline.jpg", "Images/offline.jpg" } }
};
return manifestResult;
}
}
Hope this helps.

Breeze Controller not returning proper response

I have a simple model, it is Entity Framework 5 Code First, ActiveEntity is an abstract class with an int Id property and a bool IsActive field.
public class License:ActiveEntity
{
public string LicenseName { get; set; }
public LicenseType LicenseType { get; set; }
public State State { get; set; }
public DateTime DateIssued { get; set; }
public int ValidFor { get; set; }
}
public class LicenseType:ActiveEntity
{
[StringLength(100),Required]
public string Description { get; set; }
}
public class State:ActiveEntity
{
[StringLength(2)]
[Required]
public string Name { get; set; }
[Display(Name = "Long Name")]
[Required, StringLength(25)]
public string LongName { get; set; }
}
Breeze makes a call to GetLicenses on the LicenseController:
[BreezeController]
public class LicenseController : ApiController
{
private readonly EFContextProvider<LicensingContext> db = new EFContextProvider<LicensingContext>();
[HttpGet]
public string Metadata()
{
return db.Metadata();
}
[HttpPost]
public SaveResult SaveChanges(JObject saveBundle)
{
return db.SaveChanges(saveBundle);
}
[HttpGet]
public IQueryable<License> GetLicenses()
{
//for debugging purposes
var retVal = db.Context.Licenses
.Include(l => l.State)
.Include(l=>l.LicenseType);
return retVal;
}
}
The db context returns the appropriate data but it does not appear in the response.
I don't have enough reputation points to post an image but the license type and state are in the context's response.
However the controller's response does not contain the licensetype object for the first three objects.
[{"$id":"1","$type":"Volt.Telecom.Licensing.Models.License, Volt.Telecom.Licensing.Models","LicenseName":"Low Voltage","State":{"$id":"2","$type":"Volt.Telecom.Licensing.Models.State, Volt.Telecom.Licensing.Models","Name":"FL","LongName":"Florida","IsActive":false,"Id":23},"DateIssued":"2012-11-18T00:00:00.000","ValidFor":1095,"IsActive":false,"Id":1},{"$id":"3","$type":"Volt.Telecom.Licensing.Models.License, Volt.Telecom.Licensing.Models","LicenseName":"Contractors","State":{"$ref":"2"},"DateIssued":"2012-11-18T00:00:00.000","ValidFor":1095,"IsActive":false,"Id":2},{"$id":"4","$type":"Volt.Telecom.Licensing.Models.License, Volt.Telecom.Licensing.Models","LicenseName":"General Contractors","State":{"$ref":"2"},"DateIssued":"2012-11-18T00:00:00.000","ValidFor":1095,"IsActive":false,"Id":3},{"$id":"5","$type":"Volt.Telecom.Licensing.Models.License, Volt.Telecom.Licensing.Models","LicenseName":"Low Voltage","LicenseType":{"$id":"6","$type":"Volt.Telecom.Licensing.Models.LicenseType, Volt.Telecom.Licensing.Models","Description":"Low Voltage","IsActive":false,"Id":1},"State":{"$id":"7","$type":"Volt.Telecom.Licensing.Models.State, Volt.Telecom.Licensing.Models","Name":"CA","LongName":"California","IsActive":false,"Id":35},"DateIssued":"2012-11-18T00:00:00.000","ValidFor":1095,"IsActive":false,"Id":4},{"$id":"8","$type":"Volt.Telecom.Licensing.Models.License, Volt.Telecom.Licensing.Models","LicenseName":"Contractors","LicenseType":{"$id":"9","$type":"Volt.Telecom.Licensing.Models.LicenseType, Volt.Telecom.Licensing.Models","Description":"Contractors","IsActive":false,"Id":2},"State":{"$ref":"7"},"DateIssued":"2012-11-18T00:00:00.000","ValidFor":1095,"IsActive":false,"Id":5},{"$id":"10","$type":"Volt.Telecom.Licensing.Models.License, Volt.Telecom.Licensing.Models","LicenseName":"General Contractors","LicenseType":{"$id":"11","$type":"Volt.Telecom.Licensing.Models.LicenseType, Volt.Telecom.Licensing.Models","Description":"General Contractors","IsActive":false,"Id":3},"State":{"$ref":"7"},"DateIssued":"2012-11-18T00:00:00.000","ValidFor":1095,"IsActive":false,"Id":6}]
Here is the home.js file on the client.
define(['services/logger'], function (logger) {
var system = require('durandal/system');
var serviceName = 'api/License';
// manager is the service gateway and cache holder
var manager = new breeze.EntityManager(serviceName);
var vm = {
activate: getLicenses,
title: 'Licenses',
licenses: ko.observableArray(),
includeExpired: ko.observable(false),
save: saveChanges,
show: ko.observable(false)
};
//vm.includeExpired.subscribe(getLicenses);
function getLicenses() {
log("querying Licenses", null, true);
var query = breeze.EntityQuery.from("GetLicenses");
//if (!vm.includeExpired()) {
// query = query.where("DateIssued.AddDays(ValidFor*-1)" > new Date(Date.now()));
//}
return manager
.executeQuery(query)
.then(querySucceeded)
.fail(queryFailed);
// reload vm.todos with the results
function querySucceeded(data) {
log("queried Licenses", null, true);
vm.licenses(data.results);
vm.show(true); // show the view
}
}
function queryFailed(error) {
log("Query failed: " + error.message, null, true);
}
function saveChanges() {
return manager.saveChanges()
.then(function () { log("changes saved", null, true); })
.fail(saveFailed);
}
function saveFailed(error) {
log("Save failed: " + error.message, null, true);
}
function log(msg, data, showToast) {
logger.log(msg, data, system.getModuleId(vm), showToast);
}
return vm;
//#endregion
});
Any thoughts as to why this would occur and only for the first three items, any help would be appreciated. I like breeze as a potential for some spa's we need to write but this has caused me some concern.
Update 1
If I change the order of the LicenseType_id in the database it works, the initial order was 123123
if it is changed to 312123 or 321123 all six are correct in the response
[{"$id":"1","$type":"Volt.Telecom.Licensing.Models.License, Volt.Telecom.Licensing.Models","LicenseName":"Low Voltage","LicenseType":{"$id":"2","$type":"Volt.Telecom.Licensing.Models.LicenseType, Volt.Telecom.Licensing.Models","Description":"General Contractors","IsActive":false,"Id":3},"State":{"$id":"3","$type":"Volt.Telecom.Licensing.Models.State, Volt.Telecom.Licensing.Models","Name":"FL","LongName":"Florida","IsActive":false,"Id":23},"DateIssued":"2012-11-18T00:00:00.000","ValidFor":1095,"IsActive":false,"Id":1},{"$id":"4","$type":"Volt.Telecom.Licensing.Models.License, Volt.Telecom.Licensing.Models","LicenseName":"Contractors","LicenseType":{"$id":"5","$type":"Volt.Telecom.Licensing.Models.LicenseType, Volt.Telecom.Licensing.Models","Description":"Low Voltage","IsActive":false,"Id":1},"State":{"$ref":"3"},"DateIssued":"2012-11-18T00:00:00.000","ValidFor":1095,"IsActive":false,"Id":2},{"$id":"6","$type":"Volt.Telecom.Licensing.Models.License, Volt.Telecom.Licensing.Models","LicenseName":"General Contractors","LicenseType":{"$id":"7","$type":"Volt.Telecom.Licensing.Models.LicenseType, Volt.Telecom.Licensing.Models","Description":"Contractors","IsActive":false,"Id":2},"State":{"$ref":"3"},"DateIssued":"2012-11-18T00:00:00.000","ValidFor":1095,"IsActive":false,"Id":3},{"$id":"8","$type":"Volt.Telecom.Licensing.Models.License, Volt.Telecom.Licensing.Models","LicenseName":"Low Voltage","LicenseType":{"$ref":"5"},"State":{"$id":"9","$type":"Volt.Telecom.Licensing.Models.State, Volt.Telecom.Licensing.Models","Name":"CA","LongName":"California","IsActive":false,"Id":35},"DateIssued":"2012-11-18T00:00:00.000","ValidFor":1095,"IsActive":false,"Id":4},{"$id":"10","$type":"Volt.Telecom.Licensing.Models.License, Volt.Telecom.Licensing.Models","LicenseName":"Contractors","LicenseType":{"$ref":"7"},"State":{"$ref":"9"},"DateIssued":"2012-11-18T00:00:00.000","ValidFor":1095,"IsActive":false,"Id":5},{"$id":"11","$type":"Volt.Telecom.Licensing.Models.License, Volt.Telecom.Licensing.Models","LicenseName":"General Contractors","LicenseType":{"$ref":"2"},"State":{"$ref":"9"},"DateIssued":"2012-11-18T00:00:00.000","ValidFor":1095,"IsActive":false,"Id":6}]
Edit: As of v 1.3.1 Breeze now DOES support inheritance.
The problem may be that Breeze does not yet support inheritance. There is a UserVoice suggestion here. Please vote on it. We take these suggestions very seriously.
To confirm that this is your issue, can you flatten the structure so that you do not need inheritance and see if the issue goes away.
I think that if something disappears between server to client its because Breeze retain null data column which can disorganize data structure and make knockout binding dysfunctional.
In opting to minimize Breeze performance you can place the attribute in the down level according to your need.
1 - BreezeWebApiConfig.cs level
2 - Controller level
3 - or HttGet level
This attribute works for me:
var jsonx = Breeze.WebApi.BreezeConfig.Instance;
jsonx.GetJsonSerializerSettings().NullValueHandling = Newtonsoft.Json.NullValueHandling.Include;

Resources