Json.Net TypeNameHandling.Auto and Asp Web Api Controller gives unexpected behaviour - asp.net-web-api

Assume TypeNameHandling.Auto is used for Json.net in the following Web Api Controllers:
class A {}
class B : A {}
class FooController : ApiController
{
public A Get() {
return new A();
}
}
class BarController : ApiController
{
public A Get() {
return new B();
}
}
Then I would expect the resulting Json to be:
Foo
{}
Bar
{'$type':...}
However, the output of Bar is also {}. On the other hand, if the API controller returned IEnumerable<A> and we returned a lot of B's then the type property is set.
Is it possible to change this behaviour such that it uses the return type as input to Json.Net?
A fix is to return the Json and not the object but I find that a dissatisfactory solution.

Thanks for this scenario as I think this is something which should be included by default in our Json formatter. Following is a custom Json formatter where I try to pass in the 'type' information to the Serialize method of JsonSerializer. I tried the below custom formatter with your scenario and it seemed to work fine.
(Most of the code below was cherry picked from existing Web API source code to suit your scenario.)
public class CustomJsonFormatter : JsonMediaTypeFormatter
{
public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
{
try
{
Encoding effectiveEncoding = SelectCharacterEncoding(content == null ? null : content.Headers);
if (!UseDataContractJsonSerializer)
{
using (JsonTextWriter jsonTextWriter = new JsonTextWriter(new StreamWriter(writeStream, effectiveEncoding)) { CloseOutput = false })
{
if (Indent)
{
jsonTextWriter.Formatting = Newtonsoft.Json.Formatting.Indented;
}
JsonSerializer jsonSerializer = JsonSerializer.Create(this.SerializerSettings);
jsonSerializer.Serialize(jsonTextWriter, value, type); //NOTE: passing in 'type' here
jsonTextWriter.Flush();
}
}
else
{
return base.WriteToStreamAsync(type, value, writeStream, content, transportContext);
}
return TaskHelpers.Completed();
}
catch (Exception e)
{
return TaskHelpers.FromError(e);
}
}
}
internal class TaskHelpers
{
private static readonly Task _defaultCompleted = FromResult<AsyncVoid>(default(AsyncVoid));
/// <summary>
/// Used as the T in a "conversion" of a Task into a Task{T}
/// </summary>
private struct AsyncVoid
{
}
internal static Task<TResult> FromResult<TResult>(TResult result)
{
TaskCompletionSource<TResult> tcs = new TaskCompletionSource<TResult>();
tcs.SetResult(result);
return tcs.Task;
}
/// <summary>
/// Returns an error task. The task is Completed, IsCanceled = False, IsFaulted = True
/// </summary>
internal static Task FromError(Exception exception)
{
return FromError<AsyncVoid>(exception);
}
/// <summary>
/// Returns an error task of the given type. The task is Completed, IsCanceled = False, IsFaulted = True
/// </summary>
/// <typeparam name="TResult"></typeparam>
internal static Task<TResult> FromError<TResult>(Exception exception)
{
TaskCompletionSource<TResult> tcs = new TaskCompletionSource<TResult>();
tcs.SetException(exception);
return tcs.Task;
}
/// <summary>
/// Returns a completed task that has no result.
/// </summary>
internal static Task Completed()
{
return _defaultCompleted;
}
}

I had to change Kiran's solution with the following such that arrays etc. will be written as [...] instead of {$type: IEnumerab...., values = ...}.
var contract = jsonSerializer.ContractResolver.ResolveContract(type);
// Only use the declared type if it is an object contract such that
// arrays, dictionaries, etc. aren't affected.
if (contract is JsonObjectContract)
{
jsonSerializer.Serialize(jsonTextWriter, value, type); // NOTE: passing in 'type' here
}
else
{
jsonSerializer.Serialize(jsonTextWriter, value);
}

Related

Getting public variable of popped page

I'm instancing another page, and I assign a value to one of its public properties ("SomeValue") like this:
_btnGotoOtherPage.Clicked += async (sender, e) =>
{
OtherPage _otherpage = new OtherPage;
_otherpage.SomeValue = 1033;
await Navigation.PushAsync(_otherpage);
return;
};
Within this "_otherpage", the user can modified this value.
When "_otherpage" is popped, I would like to have a look at the "SomeValue" variable and do something with it.
MessagingSystem wouldn't be what I need because I don't want to be notified on the value change. I only want to know what this value is when "_otherpage" is popped.
I would also like to not use Binding (if possible!) because I feel it's hard to organize when I'm dealing with many of such variables.
Is it possible to do this with an event perhaps?
My dream solution would be (pseudo code):
private void OnPagePopped()
{
int iNewValue = PoppedPage.SomeValue;
}
Thank you.
If you are looking for an ideal solution, I would suggest following the MVVM pattern and moving a lot of your code from your page behind to the view model.
I use an MVVM framework called FreshMvvm. This allows me to perform view model to view model navigation and to pass parameters between them like this:
await CoreMethods.PushPageModel<BPageModel>(myParameter, true);
This passes myParameter to the BPage which I can access in the Init method of the BPage View Model.
When I pop the B page (via the view model) I can pass a parameter back to the A Page
await CoreMethods.PopPageModel(myReturnParam, true);
which I can access in the ReverseInit method of APageViewModel.
Most MVVM frameworks have similar functionality.
Here are more details about FreshMvvm
This is how I do it with my popups, but it can be used with the page style in the same way.
Heres my little example, expecting MainPage = new NavigationPage(new Page1());
It is basically about having a Task on the Page where the public property sits. This task can return the value of the public property. The task will be completed in the OnDisappearing override and return the public property.
To get the value back, you push the page and await the Task page.PagePoppedTask
using System;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace MVVMTests
{
/// <summary>
/// A base implementation for a page, that holds a task,
/// which can be completed and returns a value of type of the generic T
/// </summary>
/// <typeparam name="T"></typeparam>
public class ResultContentPage<T> : ContentPage
{
public Task<T> PagePoppedTask { get { return tcs.Task; } }
private TaskCompletionSource<T> tcs;
public ResultContentPage()
{
tcs = new TaskCompletionSource<T>();
}
/// <summary>
/// Completes the task and sets it result
/// </summary>
/// <param name="result"></param>
protected /* virtual */ void SetPopupResult(T result)
{
if (PagePoppedTask.IsCompleted == false)
tcs.SetResult(result);
}
}
/// <summary>
/// Page1 exists of one button, that creates the Page2, assigns a value to a prop on the Page2
/// and then awaits the PagePoppedTask of Page2
/// </summary>
public class Page1 : ContentPage
{
public Page1()
{
var button = new Button()
{
Text = "Go to page 2"
};
button.Clicked += Button_Clicked;
Content = button;
}
private async void Button_Clicked(object sender, EventArgs e)
{
//Push the page
var newPage = new Page2() { CoolInt = 123 };
await App.Current.MainPage.Navigation.PushAsync(newPage);
//Await the result
int result = await newPage.PagePoppedTask;
System.Diagnostics.Debug.WriteLine("Page result: " + result.ToString());
}
}
/// <summary>
/// Inherits from the ResultContentPage and sets the PagePoppedTask as soon as its Disappearing
/// </summary>
public class Page2 : ResultContentPage<int>
{
public int CoolInt { get; set; } //Your property on the page
public Page2()
{
var button = new Button()
{
Text = "Go back to page 1"
};
button.Clicked += Button_Clicked;
Content = button;
}
private async void Button_Clicked(object sender, EventArgs e)
{
CoolInt = 321; //assign dummy value to CoolInt prop and pop the page
await App.Current.MainPage.Navigation.PopAsync(); //pop the page
}
protected override void OnDisappearing()
{
base.OnDisappearing();
SetPopupResult(CoolInt); //set the result of the task (in ResultContentPage<T>)
}
}
}
In Page A:
MessagingCenter.Subscribe<string>(this, "SomeMessage", (s) => HandleMessage(s));
In Page B:
MessagingCenter.Send<string>("SenderOrAnythingElse", "SomeMessage");
In PageB there is public variable:
public int SomeValue { get; set; }
Now we show PageB:
PageB nPageB = new PageB;
await Navigation.PushAsync(nPageB);
In nPageB, the user can now change PageB's public variable
//we track the "Disappearing" event of the page.
//When it occurs, we get the value
nPageB.Disappearing += async (sender1, e1) =>
{
Debug.WriteLine("New value is: " + nPage.SomeValue.ToString());
};

OdataClient SaveChanges doesn't work and results an empty Microsoft.OData.Client.DataServiceResponse

I encapsulated the odata container because of some extensions:
public class Connector
{
public string apiKey{get;set;}
public Connector(string apiKey)
{
this.apiKey = apiKey;
}
public Default.Container Get()
{
Default.Container container = new Default.Container(new Uri("http://documents.omnisoftonline.be/odata"));
container.BuildingRequest += container_BuildingRequest;
return container;
}
/// <summary>
/// When fetching a single Document, you can enable this method to including fetch the binary data
/// </summary>
/// <param name="container"></param>
/// <returns></returns>
internal bool doIO = false;
internal void container_BuildingRequest(object sender, BuildingRequestEventArgs e)
{
e.Headers.Add("X-ApiKey", apiKey);
if (doIO && e.RequestUri.ToString().Contains("/Documents"))
{
e.RequestUri = new Uri(e.RequestUri.ToString() + (e.RequestUri.Query.Contains("?") ? "&" : "?") + "doIO=true");
}
this.container_BuildingRequest(sender, e);
}
}
When i use my .dll ( using the Connector class), i have an empty result ( statuscode = -1, no headers, ...)
This is how i call the DLL
documentsConnector.Get().AddToDocuments(docToCreate);
var serviceResponse = documentsConnector.Get().SaveChanges(Microsoft.OData.Client.SaveChangesOptions.None); //tried several options
foreach(var operationResponse in serviceResponse)
{
Console.WriteLine("Response: {0}", operationResponse.StatusCode); //no operationResponses, so this isn't executed
}
It could be because my object isn't valid. But it's weird that i don't see any validation happening...
Any thoughts on how to propagate the SaveChanges() or pre-validate ( before submit) the Entity? The post isn't happening ( checked with Fiddler)
My wrapper class created a new container every time, so the entities got deleted from the Default.Container

How to search Oracle table with a string key using Entity Framework 5 and the ODP.NET Managed Provider

My table is like this (pseudo code):
CREATE TABLE DOCUMENT_LOCATOR (DOCUMENT_ID VARCHAR2(6) PRIMARY KEY)
When using the latest ODP.NET Managed Driver v121.1.2 downloaded via NuGet, the following Entity Framework commands do NOT use the index:
context.DOCUMENT_LOCATOR.Find("ABC123");
context.DOCUMENT_LOCATOR.Find(EntityFunctions.AsNonUnicode("ABC123"));
context.DOCUMENT_LOCATOR.FirstOrDefault(i => i.DOCUMENT_ID == "ABC123");
The following command does use the index:
context.DOCUMENT_LOCATOR.FirstOrDefault(i => i.DOCUMENT_ID == EntityFunctions.AsNonUnicode("ABC123"));
Unfortunately, UPDATEs and DELETEs do not use the index, so they basically render EF/Oracle useless for large tables. Has anyone figured out how to work around this?
After a fair amount of searching, I found a very nice solution. It sets all the string properties of all the entities to IsUnicode(false). To use it, you simply add one line to the OnModelCreating method of your DbContext class:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new ASSUMED_NAME_FILESMap());
modelBuilder.Configurations.Add(new ASSUMED_NAME_FILENAME_ONLYMap());
modelBuilder.Configurations.Add(new DOCUMENT_LOCATORMap());
modelBuilder.Configurations.Add(new IMS_IMAGE_TABLEMap());
modelBuilder.DisableUnicodeForAllEntityStrings(this);
}
As you can see, I changed the code to be an extension. Here's my adapted code:
public static class DbModelBuilderExtensions
{
/// <summary>
/// Disables unicode for all string properties on entities in the given context.
/// MUST BE CALLED AFTER modelBuilder.Configurations.Add(map) CALLS.
/// </summary>
/// <remarks>
/// Adapted from http://www.johncoder.com/Post/EFCodeFirstDisableUnicodeforallStringProperties
/// </remarks>
public static void DisableUnicodeForAllEntityStrings(this DbModelBuilder builder, DbContext context)
{
// Get all IDbSet<> properties from the context
var entityTypes = from property in context.GetType().GetProperties()
where property.PropertyType.ImplementsInterface(typeof(IDbSet<>))
let entityType = property.PropertyType.GetGenericArguments()[0]
select entityType;
// Disable Unicode support for each table
foreach (var entityType in entityTypes)
DisableUnicodeForEntityStrings(builder, entityType);
}
private static void DisableUnicodeForEntityStrings(DbModelBuilder modelBuilder, Type entityType)
{
// Get all string properties with setters
var stringProperties = from property in entityType.GetProperties()
where property.PropertyType == typeof(string)
&& property.CanWrite
select property;
// Each table field must be varchar for now,
// so take the string property & call IsUnicode(false).
foreach (var property in stringProperties)
{
// Don't remove this line.
// Lambda might not work without it.
PropertyInfo prop = property;
// Create the correct expression type,
// should be Expression<Func<TModel, string>>
var exprType = typeof(Expression<>)
.MakeGenericType(typeof(Func<,>)
.MakeGenericType(prop.ReflectedType, typeof(string)));
// Find and execute the Entity() method,
// using TModel generic parameter
var obj = modelBuilder.GetType()
.GetMethod("Entity")
.MakeGenericMethod(prop.ReflectedType)
.Invoke(modelBuilder, null);
// Runtime Lambda expression to represent
// something like Property(p => p.Suffix)
ParameterExpression pe = Expression.Parameter(prop.ReflectedType, "p");
var expression = Expression.Lambda(Expression.Property(pe, prop.Name), pe);
// Find the Property method that takes an expression as a parameter
// and then invoke it using the expression that was just built
var p = obj.GetType()
.GetMethod("Property", new[] { exprType })
.Invoke(obj, new[] { expression });
// If all goes well, we'll have a StringPropertyConfiguration.
var propertyConfig = p as StringPropertyConfiguration;
if (propertyConfig != null)
propertyConfig.IsUnicode(false);
}
}
public static bool ImplementsInterface(this Type value, Type interfaceType)
{
return value.GetInterface(interfaceType.FullName) != null;
}
}

How can I make CRMSvcUtil.exe generate unduplicated, error-free early-bound option sets?

I use Erik Pool's implementation of ICodeWriterFilterService and Manny Grewal's GenerateOption function as a model to filter out unwanted entities in the file that CRMSvcUtil generates. While Erik recommends returning true for the GenerateOptionSet method to generate enums for option sets, doing so duplicates any of the global option sets that are used by any particular entity (as mentioned in one of the comments on that post).
To address this, I check to see if the option set has been already generated, and if so, I return the default option (presumably false for most cases) as in the below.
//list of generated option sets, instantiated in the constructor
private List<string> GeneratedOptionSets;
public bool GenerateOptionSet
(OptionSetMetadataBase optionSetMetadata, IServiceProvider services)
{
if (!GeneratedOptionSets.Contains(optionSetMetadata.Name))
{
GeneratedOptionSets.Add(optionSetMetadata.Name);
return true;
}
return _defaultService.GenerateOptionSet(optionSetMetadata, services);
}
But when incorporating the generated file in my CRM projects, the compilation error
Cannot convert type 'Microsoft.Xrm.Sdk.OptionSetValue' to 'int'
is always thrown by every line of code that looks like
this.SetAttributeValue
("address1_shippingmethodcode", new Microsoft.Xrm.Sdk.OptionSetValue(((int)(value))));
.
As a workaround, I use a separate project where I filter the entities I need, run CRMSvcUtil with the arguments Erik suggests, replace the troublesome part of the code (int)(value) (where value is an OptionSetValue) with value.Value after the file is generated, and then resave the file, and all issues go away.
My question is this: do I need to do something differently that will fix this compilation error with the default CRMSvcUtil generated file without doing something so hackish as altering that generated file?
You can use the ICustomizeCodeDomService interface to rewrite the SetAttributeValue method for the optionSets. Snippet below:
namespace The.NameSpace
{
using System;
using System.CodeDom;
using System.Diagnostics;
using System.Linq;
using Microsoft.Crm.Services.Utility;
using Microsoft.Xrm.Sdk.Metadata;
/// <summary>
/// The customize code dom service.
/// </summary>
public sealed class CustomizeCodeDomService : ICustomizeCodeDomService
{
#region Constants and Fields
/// <summary>
/// The metadata.
/// </summary>
private IOrganizationMetadata metadata;
#endregion
#region Properties
#endregion
#region Public Methods
/// <summary>
/// The customize code dom.
/// </summary>
/// <param name="codeCompileUnit">
/// The code compile unit.
/// </param>
/// <param name="services">
/// The services.
/// </param>
public void CustomizeCodeDom(CodeCompileUnit codeCompileUnit, IServiceProvider services)
{
// Locate the namespace to use
CodeNamespace codeNamespace = codeCompileUnit.Namespaces[0];
var metadataProviderService = (IMetadataProviderService)services.GetService(typeof(IMetadataProviderService));
var filterService = (ICodeWriterFilterService)services.GetService(typeof(ICodeWriterFilterService));
this.metadata = metadataProviderService.LoadMetadata();
foreach (EntityMetadata entityMetadata in this.metadata.Entities)
{
if (filterService.GenerateEntity(entityMetadata, services))
{
CodeTypeDeclaration entityClass =
codeNamespace.Types.Cast<CodeTypeDeclaration>().First(codeType => codeType.Name.ToUpper() == entityMetadata.SchemaName.ToUpper());
UpdateEnumSetter(entityClass, entityMetadata);
}
}
}
#endregion
#region Private Methods
private static void UpdateEnumSetter(
CodeTypeDeclaration entityClass, EntityMetadata entity)
{
foreach (var attributeMetadata in entity.Attributes.Where(attributeMetadata => String.IsNullOrWhiteSpace(attributeMetadata.AttributeOf)))
{
//Match the respective field Name.
AttributeMetadata metadata1 = attributeMetadata;
foreach (
CodeTypeMember codeMembers in
entityClass.Members.Cast<CodeTypeMember>().Where(
codeMembers => codeMembers.Name == metadata1.SchemaName))
{
var codeProperty = (CodeMemberProperty)codeMembers;
if (codeProperty.HasSet)
{
if (attributeMetadata.AttributeType != null && attributeMetadata.AttributeType.Value == AttributeTypeCode.Picklist)
{
((CodeConditionStatement)codeProperty.SetStatements[1]).FalseStatements[0] =
new CodeSnippetStatement
{
Value =
String.Format(
"this.SetAttributeValue(\"{0}\", new Microsoft.Xrm.Sdk.OptionSetValue(value.Value));",
attributeMetadata.LogicalName)
};
Debug.WriteLine(String.Format("{0}.{1}", entity.LogicalName, attributeMetadata.LogicalName));
}
}
}
}
}
#endregion
}
}
Some changes to the UpdateEnumSetter method:
private static void UpdateEnumSetter(CodeTypeDeclaration entityClass, EntityMetadata entity)
{
foreach (var attributeMetadata in entity.Attributes.Where(attributeMetadata => String.IsNullOrWhiteSpace(attributeMetadata.AttributeOf)))
{
AttributeMetadata currentMetadata = attributeMetadata;
foreach (CodeTypeMember codeMembers in entityClass.Members.Cast<CodeTypeMember>().Where(codeMembers => codeMembers.Name == currentMetadata.SchemaName))
{
CodeMemberProperty codeProperty = (CodeMemberProperty)codeMembers;
if (codeProperty.HasSet)
{
if (attributeMetadata.AttributeType != null && (attributeMetadata.AttributeType.Value == AttributeTypeCode.Picklist || attributeMetadata.AttributeType.Value == AttributeTypeCode.Status))
{
if (codeProperty.SetStatements[1].GetType() == typeof(CodeConditionStatement))
{
((CodeConditionStatement)codeProperty.SetStatements[1]).FalseStatements[0] = new CodeSnippetStatement
{
Value = String.Format("this.SetAttributeValue(\"{0}\", new Microsoft.Xrm.Sdk.OptionSetValue(value.Value));", attributeMetadata.LogicalName)
};
}
else
{
codeProperty.SetStatements[1] = new CodeSnippetStatement(String.Format("this.SetAttributeValue(\"{0}\", new Microsoft.Xrm.Sdk.OptionSetValue(value.Value));", attributeMetadata.LogicalName));
}
}
}
}
}
}
I'm betting that Guarav's answer is the real way to go, but in the absence of documentation surrounding CRMSvcUtil, I'm forced to use my workaround. (I use a separate project where I filter the entities I need, run CRMSvcUtil with the arguments Erik suggests, replace the troublesome part of the code(int)(value) (where value is an OptionSetValue) with value.Value after the file is generated, and then resave the file.)
Not a perfect solution, but it's been working on the few samples I've worked with so far.
It turns out that this fault is to do with the code attempting to make optionsets that look like the code below when the types are available for use. Note the only difference is the correct type being chose for the return type and the cast.
It should be possible to update the codegen stuff to fix this bug, but it might be better to get microsoft to fix the damn thing properly, I would make a solution but I don't really have time to implement it right now because we have a mostly working solution even if we have to deal with the optionsetvalue class.
public enum entityname_optionsetname
{
Value = 200
}
[Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("myprefix_fieldname")]
public entityname_optionsetname myprefix_FieldName
{
get
{
Microsoft.Xrm.Sdk.OptionSetValue optionSet = this.GetAttributeValue<Microsoft.Xrm.Sdk.OptionSetValue>("myprefix_fieldname");
if ((optionSet != null))
{
return ((entityname_optionsetname)(System.Enum.ToObject(typeof(Microsoft.Xrm.Sdk.OptionSetValue), optionSet.Value)));
}
else
{
return null;
}
}
set
{
this.OnPropertyChanging("myprefix_FieldName");
if ((value == null))
{
this.SetAttributeValue("myprefix_fieldname", null);
}
else
{
this.SetAttributeValue("myprefix_fieldname", new Microsoft.Xrm.Sdk.OptionSetValue(((int)(value))));
}
this.OnPropertyChanged("myprefix_FieldName");
}
}
I finally am able to generate early bound class with a filtered set of entities and error free option set. I found the bulk of my answer through this thread, so thanks guys. The problem though is it's difficult to compile all of the various suggestions into something that actually... compiles. So I thought I'd post my final solution for the benefit of others, here's what worked for me.
I used Erik Pool's, Manny Grewal's, and Peter Majeed's solution for outputting only distinct enums with proper values, then combined that with Gaurav Dalal's solution (updated by JFK007 to fix the cast error) to re-write the SetAttributeValue that caused the (int)(value) error. And as an added bonus, I used the same solution for filtering distinct option sets to also filter for distinct option set values (which was an issue in my org).
The result is a class library containing CodeWriterFilter and CustomizeCodeDomService, the cmd batch file to run CrmSvcUtil.exe, and the filter.xml to filter the entities.
In your class library add references to CrmSvcUtil.exe, Microsoft.Xrm.Sdk, and System.Runtime.Serialization then compile the dll and copy it to the same the folder as your CrmSvcUtil.exe. Use the command I've included to reference your new assembly and build the early bound class file.
CodeWriterFilter:
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using Microsoft.Crm.Services.Utility;
using Microsoft.Xrm.Sdk.Metadata;
using System.Text.RegularExpressions;
using Microsoft.Xrm.Sdk;
namespace SvcUtilFilter
{
/// <summary>
/// CodeWriterFilter for CrmSvcUtil that reads list of entities from an xml file to
/// determine whether or not the entity class should be generated.
/// </summary>
public class CodeWriterFilter : ICodeWriterFilterService
{
//list of entity names to generate classes for.
private HashSet<string> _validEntities = new HashSet<string>();
//reference to the default service.
private ICodeWriterFilterService _defaultService = null;
//list of generated option sets, instantiated in the constructor
private List<string> GeneratedOptionSets;
//list of generated options, instantiated in the constructor
private List<string> GeneratedOptions;
/// <summary>
/// constructor
/// </summary>
/// <param name="defaultService">default implementation</param>
public CodeWriterFilter(ICodeWriterFilterService defaultService)
{
this._defaultService = defaultService;
this.GeneratedOptionSets = new List<string>();
this.GeneratedOptions = new List<string>();
LoadFilterData();
}
/// <summary>
/// loads the entity filter data from the filter.xml file
/// </summary>
private void LoadFilterData()
{
XElement xml = XElement.Load("filter.xml");
XElement entitiesElement = xml.Element("entities");
foreach (XElement entityElement in entitiesElement.Elements("entity")) {
_validEntities.Add(entityElement.Value.ToLowerInvariant());
}
}
/// <summary>
/// /Use filter entity list to determine if the entity class should be generated.
/// </summary>
public bool GenerateEntity(EntityMetadata entityMetadata, IServiceProvider services)
{
return (_validEntities.Contains(entityMetadata.LogicalName.ToLowerInvariant()));
}
//All other methods just use default implementation:
public bool GenerateAttribute(AttributeMetadata attributeMetadata, IServiceProvider services)
{
return _defaultService.GenerateAttribute(attributeMetadata, services);
}
public bool GenerateOption(OptionMetadata optionMetadata, IServiceProvider services)
{
//return _defaultService.GenerateOption(optionMetadata, services);
string label = optionMetadata.Label.UserLocalizedLabel.Label;
//remove spaces and special characters
label = Regex.Replace(label, #"[^a-zA-Z0-9]", string.Empty);
if (label.Length > 0 && !char.IsLetter(label, 0)) {
label = "Number_" + label;
}
else if (label.Length == 0) {
label = "empty";
}
if (!GeneratedOptions.Exists(l=>l.Equals(label))) {
GeneratedOptions.Add(label);
optionMetadata.Label = new Label(label, 1033);
return _defaultService.GenerateOption(optionMetadata, services);
}
else { return false; }
}
public bool GenerateOptionSet(OptionSetMetadataBase optionSetMetadata, IServiceProvider services)
{
//return _defaultService.GenerateOptionSet(optionSetMetadata, services);
if (!GeneratedOptionSets.Contains(optionSetMetadata.Name)) {
GeneratedOptionSets.Add(optionSetMetadata.Name);
return true;
}
return _defaultService.GenerateOptionSet(optionSetMetadata, services);
}
public bool GenerateRelationship(RelationshipMetadataBase relationshipMetadata, EntityMetadata otherEntityMetadata, IServiceProvider services)
{
return _defaultService.GenerateRelationship(relationshipMetadata, otherEntityMetadata, services);
}
public bool GenerateServiceContext(IServiceProvider services)
{
return _defaultService.GenerateServiceContext(services);
}
}
}
CustomizeCodeDomService:
using System;
using System.CodeDom;
using System.Diagnostics;
using System.Linq;
using Microsoft.Crm.Services.Utility;
using Microsoft.Xrm.Sdk.Metadata;
namespace SvcUtilFilter
{
/// <summary>
/// The customize code dom service.
/// </summary>
public sealed class CustomizeCodeDomService : ICustomizeCodeDomService
{
#region Constants and Fields
/// <summary>
/// The metadata.
/// </summary>
private IOrganizationMetadata metadata;
#endregion
#region Properties
#endregion
#region Public Methods
/// <summary>
/// The customize code dom.
/// </summary>
/// <param name="codeCompileUnit">
/// The code compile unit.
/// </param>
/// <param name="services">
/// The services.
/// </param>
public void CustomizeCodeDom(CodeCompileUnit codeCompileUnit, IServiceProvider services)
{
// Locate the namespace to use
CodeNamespace codeNamespace = codeCompileUnit.Namespaces[0];
var metadataProviderService = (IMetadataProviderService)services.GetService(typeof(IMetadataProviderService));
var filterService = (ICodeWriterFilterService)services.GetService(typeof(ICodeWriterFilterService));
this.metadata = metadataProviderService.LoadMetadata();
foreach (EntityMetadata entityMetadata in this.metadata.Entities) {
if (filterService.GenerateEntity(entityMetadata, services)) {
CodeTypeDeclaration entityClass =
codeNamespace.Types.Cast<CodeTypeDeclaration>().First(codeType => codeType.Name.ToUpper() == entityMetadata.SchemaName.ToUpper());
UpdateEnumSetter(entityClass, entityMetadata);
}
}
}
#endregion
#region Private Methods
private static void UpdateEnumSetter(CodeTypeDeclaration entityClass, EntityMetadata entity)
{
foreach (var attributeMetadata in entity.Attributes.Where(attributeMetadata => String.IsNullOrWhiteSpace(attributeMetadata.AttributeOf))) {
AttributeMetadata currentMetadata = attributeMetadata;
foreach (CodeTypeMember codeMembers in entityClass.Members.Cast<CodeTypeMember>().Where(codeMembers => codeMembers.Name == currentMetadata.SchemaName)) {
CodeMemberProperty codeProperty = (CodeMemberProperty)codeMembers;
if (codeProperty.HasSet) {
if (attributeMetadata.AttributeType != null && (attributeMetadata.AttributeType.Value == AttributeTypeCode.Picklist || attributeMetadata.AttributeType.Value == AttributeTypeCode.Status)) {
if (codeProperty.SetStatements[1].GetType() == typeof(CodeConditionStatement)) {
((CodeConditionStatement)codeProperty.SetStatements[1]).FalseStatements[0] = new CodeSnippetStatement {
Value = String.Format("this.SetAttributeValue(\"{0}\", new Microsoft.Xrm.Sdk.OptionSetValue(value.Value));", attributeMetadata.LogicalName)
};
}
else {
codeProperty.SetStatements[1] = new CodeSnippetStatement(String.Format("this.SetAttributeValue(\"{0}\", new Microsoft.Xrm.Sdk.OptionSetValue(value.Value));", attributeMetadata.LogicalName));
}
}
}
}
}
}
#endregion
}
}
CrmSvcUtil_run.cmd Command Batch File:
#echo off
set url=https://[organization].api.crm.dynamics.com/XRMServices/2011/Organization.svc
echo.
echo Generating CrmSvcUtil Proxy class in output folder
echo.
CrmSvcUtil.exe /metadataproviderservice:"MetadataProvider.IfdMetadataProviderService,
MetadataProvider"
/url:https://[organization].api.crm.dynamics.com/XRMServices/2011/Organization.svc /out:Xrm.cs
/namespace:Xrm /serviceContextName:XrmServiceContext /serviceContextPrefix:Xrm
/u:[username] /p:[password]
/codewriterfilter:SvcUtilFilter.CodeWriterFilter,SvcUtilFilter
/codecustomization:SvcUtilFilter.CustomizeCodeDomService,SvcUtilFilter
echo.
pause
filter.xml
<filter>
<entities>
<entity>systemuser</entity>
<entity>team</entity>
<entity>role</entity>
<entity>businessunit</entity>
<entity>account</entity>
<entity>product</entity>
<entity>transactioncurrency</entity>
</entities>
</filter>
It looks like there was a bug in the crmsrvcutil that has since been fixed. My code for OptionSet properties now looks like this:
[Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("prioritycode")]
public Microsoft.Xrm.Sdk.OptionSetValue PriorityCode
{
get
{
return this.GetAttributeValue<Microsoft.Xrm.Sdk.OptionSetValue>("prioritycode");
}
set
{
this.OnPropertyChanging("PriorityCode");
this.SetAttributeValue("prioritycode", value);
this.OnPropertyChanged("PriorityCode");
}
}
And I get no error setting the OptionSetValue...

Update SubmitChanges() - does not update

With this code there is no change in my database.
when the above code is run there is no longer a new entry created neither is an entry updated.
public void UpdateCallback(callback cb_)
{
callback call = context.callbacks.Single(c => c.callbackID == cb_.callbackID);
//call.callbackID = cb_.callbackID;
call.status = cb_.status;
call.contactName = cb_.contactName;
call.company = cb_.company;
call.phone = cb_.phone;
call.calledDate = cb_.calledDate;
call.callback1 = cb_.callback1;
call.notes = cb_.notes;
try
{
context.SubmitChanges();
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
This post is similar to yours.
In his case, the update did not work because the table did not have a primary key.
Have you verified that CallbackId is defined as a PK in the database?
Nothing immediate jumps out at me. I have found it useful to use the Log property of the DataContext to see the SQL being generated.
See http://msdn.microsoft.com/en-us/library/system.data.linq.datacontext.log.aspx
You could then use something like the code below to output the SQL to the Visual Studio Debug window while debugging.
/// <summary>
/// Implementation of a <see cref="TextWriter"/> that outputs to the debug window
/// </summary>
public class DebugTextWriter : TextWriter
{
public override void Write(char[] buffer, int index, int count)
{
System.Diagnostics.Debug.Write(new string(buffer, index, count));
}
public override void Write(string value)
{
System.Diagnostics.Debug.Write(value);
}
public override Encoding Encoding
{
get { return System.Text.Encoding.Default; }
}
}

Resources