I am using Microsoft.OData.Client based on the microsoft sample application.
Here's my simple WebAPI Controller:
[Route("test")]
[HttpGet]
public IHttpActionResult Test()
{
var context = _dynamicsContextFactory.CreateContext();
// adding this had no effect // context.MergeOption = MergeOption.AppendOnly;
// adding this had no effect // context.MergeOption = MergeOption.OverwriteChanges;
// adding this had no effect // context.MergeOption = MergeOption.NoTracking;
// adding this had no effect // context.MergeOption = MergeOption.PreserveChanges;
var result = context.SalesOrderHeadersV2.Expand("SalesOrderLines").Take(1).ToList();
return Ok(result);
}
The client generates the correct URL.
https://example.com/data/SalesOrderHeadersV2?$top=1&$expand=SalesOrderLines
I can see in fiddler the SalesOrderLines property returned in the JSON.
However when I inspect the result variable (or view the output) there is no SalesOrderLines property. So the order lines have not been mapped into my result object from the data downloaded from the oData source.
Important Note: I am using EDMXTrimmer to reduce the number of entities in my client, could this be an issue If I’m missing a joining entity? (It seems unlikely there's a joining entity in this case)
Clue?
When I try to change this line:
var result = context.SalesOrderHeadersV2.Expand(x=>x.SalesOrderLines).Take(1).ToList();
It will not compile because 'SalesOrderHeaderV2' does not contain a definition for 'SalesOrderLines' ...
Note: context.SalesOrderLines does exist.
The issue was that EDMXTrimmer removed the navigation properties.
EDMXTrimmer has since been fixed.
Related
I have created an ASP.NET Core Web Api backend with an Angular 7 frontend. One of the methods in the Api can return either an object or an array to an Angular service. How do I route to specific observable, based on the data type returned? I am a noob to Angular, so any kind assistance would be appreciated.
Angular service call to Api:
getLinksFromSitus(situs: any) {
this.http.post(this.baseUrl + 'getLinksFromSitus', situs).subscribe(data =>
this.apiData.next(data)
);
}
Portion of Web Api that returns array if more than one APN present:
// if more than one item in list, get status information for each and return list to user to select appropriate apn
if (propApn.Count > 1)
{
return Ok(propApn);
}
Portion of same method to return object if only one value for APN:
var resultsModel = new Results
{
ArcGisLink = arcGisLink,
HistInfoLink = histInfoLink,
PropInfoLink = propInfoLink
};
return Ok(resultsModel);
You can't do this. Typescript can only type things based on static analysis at build time, what your describing would require Typescript to know the result of your API call at build time, which it doesn't do.
The best you can do is indicating that your API call can return both of your them:
public myApiFunc(req: MyRequestModel): Observable<any>
But that will still require you to figure out which type returned at runtime.
I was able to find a solution that worked...
getLinksFromSitus(situs: any) {
this.http.post(this.baseUrl + 'getLinksFromSitus', situs).subscribe(data => {
if (data.hasOwnProperty('arcGisLink')) {
this.apiData.next(data);
} else {
let vals = [];
vals = this.apiPropApn.getValue();
const item = vals.concat(data);
this.apiPropApn.next(item);
}
});
}
So, after subscribing to the HttpResponse, I am able to check if the data in the response contains a known property. If it doesn't contain the known property, then it concatenates the data to a BehaviorSubject array. It works perfectly.
I defined a new type of model element as a plug-in; let's refer to it as Foo. A Foo node in the model should translate to a section element in the view. So far, so good. I managed to do that by defining simple conversion rules. I also managed to define a new FooCommand that transforms (renames) selected blocks to Foo.
I got stuck trying to have attributes on those Foo model nodes be translated to attributes on the view elements (and vice-versa). Suppose Foos have an attribute named fooClass which should map to the view element's class attribute.
<Foo fooClass="green-foo"> should map to/from <section class="green-foo">
I can successfully receive parameters in FooCommand, but I can't seem to set them on the blocks being processed by the command:
execute(options = {}) {
const document = this.editor.document;
const fooClass = options.fooClass;
document.enqueueChanges(() => {
const batch = options.batch || document.batch();
const blocks = (options.selection || document.selection).getSelectedBlocks();
for (const block of blocks) {
if (!block.is('foo')) {
batch.rename(block, 'foo');
batch.setAttribute(block, 'fooClass', fooClass);
}
}
});
}
Below is the code for the init function in the Foo plugin, including the model→view and view→model conversions:
init() {
const editor = this.editor;
const doc = editor.document;
const data = editor.data;
const editing = editor.editing;
editor.commands.add('foo', new FooCommand(editor));
doc.schema.registerItem('foo', '$block');
buildModelConverter().for(data.modelToView, editing.modelToView)
.fromElement('foo')
.toElement(modelElement => {
const fooClass = modelElement.item.getAttribute('fooClass'));
return new ContainerElement('section', {'class': fooClass});
});
buildViewConverter().for(data.viewToModel)
.fromElement('section')
.toElement(viewElement => {
let classes = Array.from(viewElement.getClassNames());
let modelElement = new ModelElement('foo', {'fooClass': classes[0]});
return modelElement;
});
}
When I try to run the command via
editor.execute('foo', { fooClass: 'green-foo' })
I can see that the green-foo value is available to FooCommand, but the modelElement in the model→view conversion, on the other hand, has no fooClass attribute.
I'm sure I'm missing the point here and misusing the APIs. I'd be really thankful if someone could shed some light on this issue. I can provide more details, as needed.
Follow-up after initial suggestions
Thanks to #Reinmar and #jodator for their suggestion regarding configuring the document schema to allow for the custom attribute. I really thought that would have taken care of it, but no. It may have been a necessary step anyway, but I'm still unable to get the attribute value from the model element during the model→view conversion.
First, let me add an important piece of information I had left out: the CKEditor5's version I'm working with is 1.0.0-alpha2. I am aware several of the APIs are bound to change, but I would still like to get things working with the present version.
Model→view conversion
If I understand it correctly, one can either pass a string or a function to the toElement call. A question about using the latter: what exactly are the parameters passed to the function? I assumed it would be the model element (node?) to be converted. Is that the case? If so, why is the attribute set on that node via batch.setAttribute (inside a document.enqueueChanges) not available when requested? Should it be?
A sequencing problem?
Additional testing seems to indicate there's some kind of order-of-execution issue happening. I've observed that, even though the attribute is not available when I first try to read it from the modelElement parameter, it will be so if I read it again later. Let me try to illustrate the situation below. First, I'll modify the conversion code to make it use some dummy value in case the attribute value is not available when read:
buildModelConverter().for(data.modelToView, editing.modelToView)
.fromElement('foo')
.toElement(modelElement => {
let fooClass = modelElement.item.getAttribute('fooClass') || 'naught';
let viewElement = new ContainerElement('section');
viewElement.setAttribute('class', fooClass);
return viewElement;
});
Now I reload the page and execute the following instructions on the console:
c = Array.from(editor.document.getRoot().getChildren());
c[1].is('paragraph'); // true
// Changing the node from 'paragraph' to 'foo' and adding an attribute
// 'fooClass' with value 'green-foo' to it.
editor.document.enqueueChanges(() => {
const batch = editor.document.batch();
batch.rename(c[1], 'foo');
batch.setAttribute(c[1], 'fooClass', 'green-foo');
return batch;
});
c[1].is('paragraph'); // false
c[1].is('foo'); // true
c[1].hasAttribute('fooClass'); // true
c[1].getAttribute('fooClass'); // 'green-foo'
Even though it looks like the expected output is being produced, a glance at the generated view element shows the problem:
<section class="naught"/>
Lastly, even if I try to reset the fooClass attribute on the model element, the change is not reflected on the view element. Why is that? Shouldn't changes made via enqueueChanges cause the view to update?
Sorry for the very long post, but I'm trying to convey as many details as I can. Here's hoping someone will spot my mistake or misunderstanding of how the CKEditor 5's API actually works.
View not updating?
I turned to Document's events and experimented with the changesDone event. It successfully addresses the "timing" issue, as it consistently triggers only after all changes have been processed. Still, the problem of the view not updating in response to a change in the model remains. To make it clear, the model does change, but the view does not reflect that. Here is the call:
editor.document.enqueueChanges(() => editor.document.batch().setAttribute(c[1], 'fooClass', 'red-foo'));
To be 100% sure I wrote the whole feature myself. I use the 1.0.0-beta.1 API which is completely different than what you had.
Basically – it works. It isn't 100% correct yet, but I'll get to that.
How to convert an element+attribute pair?
The thing when implementing a feature which needs to convert element + attribute is that it requires handling the element and attribute conversion separately as they are treated separately by CKEditor 5.
Therefore, in the code below you'll find that I used elementToElement():
editor.conversion.elementToElement( {
model: 'foo',
view: 'section'
} );
So a converter between model's <foo> element and view's <section> element. This is a two-way converter so it handles upcasting (view -> model) and downcasting (model -> view) conversion.
NOTE: It doesn't handle the attribute.
Theoretically, as the view property you could write a callback which would read the model element's attribute and create view element with this attribute set too. But that wouldn't work because such a configuration would only make sense in case of downcasting (model -> view). How could we use that callback to downcast a view structure?
NOTE: You can write converters for downcast and upcast pipelines separately (by using editor.conversion.for()), in which case you could really use callbacks. But it doesn't really make sense in this case.
The attribute may change independently!
The other problem is that let's say you wrote an element converter which sets the attribute at the same time. Tada, you load <section class=ohmy> and gets <foo class=ohmy> in your model.
But then... what if the attribute will change in the model?
In the downcast pipeline CKEditor 5 treats element changes separately from attribute changes. It fires them as separate events. So, when your FooCommand is executed on a heading it calls writer.rename() and we get the following events in DowncastDispatcher:
remove with <heading>
insert:section
But then the attribute is changed too (writer.setAttribute()), so we also get:
setAttibute:class:section
The elementToElement() conversion helper listens to insert:section event. So it's blind to setAttribute:class:selection.
Therefore, when you change the value of the attribute, you need the attributeToAttribute() conversion.
Sequencing
I didn't want to reply to your question before we released 1.0.0-beta.1 because 1.0.0-beta.1 brought the Differ.
Before 1.0.0-beta.1 all changes were converted immediately when they were applied. So, rename() would cause immediate remove and insert:section events. At this point, the element that you got in the latter one wouldn't have the class attribute set yet.
Thanks to the Differ we're able to start the conversion once all the changes are applied (after change() block is executed). This means that the insert:section event is fired once the model <foo> element has the class attribute set already. That's why you could write a callback-based converters... bur you shouldn't :D
The code
import { downcastAttributeToAttribute } from '#ckeditor/ckeditor5-engine/src/conversion/downcast-converters';
import { upcastAttributeToAttribute } from '#ckeditor/ckeditor5-engine/src/conversion/upcast-converters';
class FooCommand extends Command {
execute( options = {} ) {
const model = this.editor.model;
const fooClass = options.class;
model.change( writer => {
const blocks = model.document.selection.getSelectedBlocks();
for ( const block of blocks ) {
if ( !block.is( 'foo' ) ) {
writer.rename( block, 'foo' );
writer.setAttribute( 'class', fooClass, block );
}
}
} );
}
}
class FooPlugin extends Plugin {
init() {
const editor = this.editor;
editor.commands.add( 'foo', new FooCommand( editor ) );
editor.model.schema.register( 'foo', {
allowAttributes: 'class',
inheritAllFrom: '$block'
} );
editor.conversion.elementToElement( {
model: 'foo',
view: 'section'
} );
editor.conversion.for( 'upcast' ).add(
upcastAttributeToAttribute( {
model: 'class',
view: 'class'
} )
);
editor.conversion.for( 'downcast' ).add(
downcastAttributeToAttribute( {
model: 'class',
view: 'class'
} )
);
// This should work but it does not due to https://github.com/ckeditor/ckeditor5-engine/issues/1379 :(((
// EDIT: The above issue is fixed and will be released in 1.0.0-beta.2.
// editor.conversion.attributeToAttribute( {
// model: {
// name: 'foo',
// key: 'class'
// },
// view: {
// name: 'section',
// key: 'class'
// }
// } );
}
}
This code works quite well, except the fact that it converts the class attribute on any possible element that has it. That's because I had to use very generic downcastAttributeToAttribute() and upcastAttributeToAttribute() converters because of a bug that I found (EDIT: it's fixed and will be available in 1.0.0-beta.2). The commented out piece of code is how you it should be defined if everything worked fine and it will work in 1.0.0-beta.2.
It's sad that we missed such a simple case, but that's mainly due to the fact that all our features... are much more complicated than this.
Collaboration Mode:
What is the best way to propagate changes from Client #1's canvas to client #2's canvas? Here's how I capture and send events to Socket.io.
$scope.canvas.on('object:modified',function(e) {
Socket.whiteboardMessage({
eventId:'object:modified',
event:e.target.toJSON()
});
});
On the receiver side, this code works splendidly for adding new objects to the screen, but I could not find documentation on how to select and update an existing object in the canvas.
fabric.util.enlivenObjects([e.event], function(objects) {
objects.forEach(function(o) {
$scope.canvas.add(o);
});
});
I did see that Objects have individual setters and one bulk setter, but I could not figure out how to select an existing object based on the event data.
Ideally, the flow would be:
Receive event with targeted object data.
Select the existing object in the canvas.
Perform bulk update.
Refresh canvas.
Hopefully someone with Fabric.JS experience can help me figure this out. Thanks!
UPDATED ANSWER - Thanks AJM!
AJM was correct in suggesting a unique ID for every newly created element. I was also able to create a new ID for all newly created drawing paths as well. Here's how it worked:
var t = new fabric.IText('Edit me...', {
left: $scope.width/2-100,
top: $scope.height/2-50
});
t.set('id',randomHash());
$scope.canvas.add(t);
I also captured newly created paths and added an id:
$scope.canvas.on('path:created',function(e) {
if (e.target.id === undefined) {
e.target.set('id',randomHash());
}
});
However, I encountered an issue where my ID was visible in console log, but it was not present after executing object.toJSON(). This is because Fabric has its own serialization method which trims down the data to a standardized list of properties. To include additional properties, I had to serialize the data for transport like so:
$scope.canvas.on('object:modified',function(e) {
Socket.whiteboardMessage({
object:e.target.toJSON(['id']) // includes "id" in output.
})
});
Now each object has a unique ID with which to perform updates. On the receiver's side of my code, I added AJM's object-lookup function. I placed this code in the "startup" section of my application so it would only run once (after Fabric.js is loaded, of course!)
fabric.Canvas.prototype.getObjectById = function (id) {
var objs = this.getObjects();
for (var i = 0, len = objs.length; i < len; i++) {
if (objs[i].id == id) {
return objs[i];
}
}
return 0;
};
Now, whenever a new socket.io message is received with whiteboard data, I am able to find it in the canvas via this line:
var obj = $scope.canvas.getObjectById(e.object.id);
Inserting and removing are easy, but for updating, this final piece of code did the trick:
obj.set(e.object); // Updates properties
$scope.canvas.renderAll(); // Redraws canvas
$scope.canvas.calcOffset(); // Updates offsets
All of this required me to handle the following events. Paths are treated as objects once they're created.
$scope.canvas.on('object:added',function(e) { });
$scope.canvas.on('object:modified',function(e) { });
$scope.canvas.on('object:moving',function(e) { });
$scope.canvas.on('object:removed',function(e) { });
$scope.canvas.on('path:created',function(e) { });
I did something similar involving a single shared canvas between multiple users and ran into this exact issue.
To solve this problem, I added unique IDs (using a javascript UUID generator) to each object added to the canvas (in my case, there could be many users working on a canvas at a time, thus I needed to avoid collisions; in your case, something simpler could work).
Fabric objects' set method will let you add an arbitrary property, like an id: o.set('id', yourid). Before you add() a new Fabric object to your canvas (and send that across the wire), tack on an ID property. Now, you'll have a unique key by which you can pick out individual objects.
From there, you'd need a method to retrieve an object by ID. Here's what I used:
fabric.Canvas.prototype.getObjectById = function (id) {
var objs = this.getObjects();
for (var i = 0, len = objs.length; i < len; i++) {
if (objs[i].id == id) {
return objs[i];
}
}
return null;
};
When you receive data from your socket, grab that object from the canvas by ID and mutate it using the appropriate set methods or copying properties wholesale (or, if getObjectById returns null, create it).
I'm using MVCSiteMapProvider v4.6.22 and have a dynamic node provider for one of my controllers.
Something like:
public class ProviderDetailsNodeProvider : DynamicNodeProviderBase
{
public override IEnumerable<DynamicNode> GetDynamicNodeCollection(ISiteMapNode node)
{
foreach (var provider in providers)
{
var dn = new DynamicNode()
{
Title = provider.Name,
ParentKey = "ParentKey",
Key = $"provider_master_{provider.ID}",
CanonicalUrl = "/url/something"
};
dn.RouteValues.Add("myRouteParamName", "myRouteParamValue");
yield return dn;
}
}
}
Without setting the CanonicalKey or CanonicalUrl properties of the DynamicNode, I get the correct behaviour. However I now wish to have multiple URLs pointing at the same content so I need to utilise the Canonical URL features of MVCSiteMapProvider.
If I attempt to set the CanonicalUrl as in the above snippet, or the CanonicalKey (my preferred choice), then when I attempt to use the helper methods, such as:
#Html.MvcSiteMap().SiteMapPath()
I get a NullReferenceException - it's the #Html.MvcSiteMap() which returns null.
What am I doing incorrectly, why do I get this NullReferenceException just by setting these properties against my dynamic nodes?
I'm using the MvcSiteMapProvider.MVC5 package, in an MVC6 application. I can't see a newer version on Nuget.
MVC 6 is not yet supported, as per the issue on NuGet.
I'm using Linq and having trouble doing something that I believe should be trivial. I want to return data from one layer so it can be used independently of linq in another layer.
Suppose I have a Data Access Layer. It knows about the entity framework and how to interact with it. But, it doesn't care who accesses it. The one interesting requirement I have is that the queries in the entity framework return projected data that is not part of the Entity Model itself. Please don't ask me to change this part of the requirement and make POCOs for each return type, as it is not the best design given the problem I am trying to solve. Below is an example.
public class ChartData
{
public function <<returnType??>> GetData()
{
MyEntities context = new MyEntities();
var results = from context.vManyColumnsOfData as v
where v.CompanyName = "acme"
select new {Year = v.SalesYear, Income = v.Income};
return ??;
}
}
Then, I would like to have an ASP.Net UI layer be able to call into the Data Access Layer to get the data in order to bind it to a control. The UI layer should have no notion of where the data came from. It should only know that it has the data it needs to bind. Below is an example.
protected void chart_Load(object sender, EventArgs e)
{
// set some chart properties
chart.Skin = "Default";
...
// Set the data source
ChartData dataMgr = new ChartData();
<<returnType?>> data = dataMgr.GetData();
chart.DataSource = data;
chart.DataBind();
}
What is the best way to send linq projected data back to another layer?
If you don't need to use the projected type statically, just return IEnumerable<object>.
Please don't ask me to change this part of the requirement and make
POCOs for each return type, as it is not the best design given the
problem I am trying to solve.
I feel like I should rightly ignore this, as the best thing to do is to return a defined type. Anonymous types are useful when they are wholly contained within the method that creates them. Once you start passing them around, it is time to go ahead and give them the proper class treatment.
However, to live within your imposed limitations, you can return IEnumerable<object> from the method and use that or var at the callsite and rely upon the dynamic binding of the control to get at the data. It's not going to help you if you need to deal with the object programmatically, but it will serve fine for databinding.
You can not return an anonymous type, so basically for this you will need POCO's even though you don't want them.
"not the best design given the problem I am trying to solve"
Could you explain what you are trying to achieve a little more? It might be possible to return some type of list containing a dictionary of items (ie rows and columns). Think something like an untyped dataset (yuck)
Your GetData method can use IEnumerable (the "old" non-generic interface) as its return type.
Any dynamic resolution (e.g. ASP.NET or XAML bindings) should work as expected, which seems to be what you want to do.
However, if you want to use the results in your code, you will probably have to resort to .NET 4's dynamic keyword.
The following example can be run in LINQPad (in "C# Program" mode) and illustrates this:
void Main()
{
var v = GetData();
foreach (dynamic element in v)
{
((string)element.Name).Dump();
}
}
public IEnumerable GetData()
{
return from i in Enumerable.Range(1, 10)
select new
{
Name = "Item " + i,
Value = i
};
}
Keep in mind that, design-wise, coding like this will make most people frown and can affect performance.