Blog

Feb 16
WebAPI ODAta Part 6 - Hook it up to LightSwitch - Solved

WebAPI OData Part 1 - Setup Sample database

WebAPI OData Part 2 - Setup Entity Framework

WebAPI OData Part 3 - Expose OData with RESTier

WebAPI OData Part 4 - Expose OData V3

WebAPI OData Part 5 - Hook it up to LightSwitch - Fail

WebAPI OData Part 6 - Hook it up to LightSwitch - Solved< You are here

I the previous artice we were able to connect our WebAPI OData to LightSwitch but due to a limitation and bug in ODataConventionModelBuilder, the relationships in the metadata were not bi-directional andLS didn't like em.

The solution is to build the metadata using Csdl.EdmxReader as in this obscure bit of code:

https://gist.github.com/raghuramn/5864013

and here is something similar:

https://gist.github.com/dariusclay/8673940

I used bits from each of these to build the model and found the OData metadata is much better than that output from ODataConventionModelBuilder. 

Next, LS  Pukes when there is no

m:IsDefaultEntityContainer="true" attribute on the EntityContainer node so that that was handled using an old WCF ODataUtils library. 

I'll post the code below but please get it from here:

https://gist.github.com/joshbooker/5ff03b12e77ff93bea14

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;

using System.Web.Http.OData.Builder;
using System.Web.Http.OData.Batch;
using System.Web.Http.OData.Extensions;
using ODataSample.Models;

using System.IO;
using System.Xml;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using Microsoft.Data.Edm;
//using Microsoft.Data.Edm.Csdl;
using Microsoft.Data.Edm.Validation;
using Microsoft.Data.OData;

namespace ODataSample
{
    using Microsoft.Restier.WebApi;
    using Microsoft.Restier.WebApi.Batch;
    using Controllers;

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.MapHttpAttributeRoutes();
            RegisterAdventureWorksV4(config, GlobalConfiguration.DefaultServer);
            RegisterAdventureWorksV3(config, GlobalConfiguration.DefaultServer);
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }

        public static async void RegisterAdventureWorksV4(HttpConfiguration config, HttpServer server)
        {
            await config.MapODataDomainRoute(
               "AdventureWorksV4", "AdventureWorks/v4",
                new ODataDomainBatchHandler(server));
        }
        /**/
        public static void RegisterAdventureWorksV3(HttpConfiguration config, HttpServer server)
        {
            /*
            ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
            builder.EntitySet
      
      
("Addresses"); builder.EntitySet("AddressTypes"); builder.EntitySet("BusinessEntities"); builder.EntitySet("BusinessEntityContacts"); builder.EntitySet("BusinessEntityAddresses"); builder.EntitySet("EmailAddresses"); builder.EntitySet("People"); builder.EntitySet("PersonPhones"); builder.EntitySet("PhoneNumberTypes"); builder.EntitySet("Passwords"); builder.EntitySet("StateProvinces"); builder.EntitySet("CountryRegions"); builder.EntitySet("ContactTypes"); //HttpServer server = new HttpServer(config); config.Routes.MapODataServiceRoute("AdventureWorksV3", "AdventureWorks/v3", builder.GetEdmModel(), new DefaultODataBatchHandler(server)); */ IEdmModel model = GetEdmModel(); config.Routes.MapODataServiceRoute("AdventureWorksV3", "AdventureWorks/v3", model, new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer)); } public static IEdmModel GetEdmModel() where T : DbContext, new() { IEdmModel model; DbContext context = new T(); string contextName = new Stack(context.ToString().Split('.')).Pop(); using (MemoryStream stream = new MemoryStream()) { using (XmlWriter writer = XmlWriter.Create(stream)) { EdmxWriter.WriteEdmx(context, writer); writer.Close(); stream.Seek(0, SeekOrigin.Begin); using (XmlReader reader = XmlReader.Create(stream)) { model = Microsoft.Data.Edm.Csdl.EdmxReader.Parse(reader); //LS pukes when there's no IEdmEntityContainer defaultContainer = model.FindDeclaredEntityContainer(contextName); ODataUtils.SetIsDefaultEntityContainer(model,defaultContainer , true); } } } return model; } } }

 

Feb 13
WebAPI OData Part 5 - Hook it up to LightSwitch - FAIL

WebAPI OData Part 1 - Setup Sample database

WebAPI OData Part 2 - Setup Entity Framework

WebAPI OData Part 3 - Expose OData with RESTier

WebAPI OData Part 4 - Expose OData V3

WebAPI OData Part 5 - Hook it up to LightSwitch - Fail< You are here

WebAPI OData Part 6 - Hook it up to LightSwitch - Solved

​Okay So Lightswithch Allows OData source and our V3 endpoint connects just fine, but there are issues with the navigation properties / relationships.

WebAPI has what I'd call a limitation and also a bug which together make a simple one-to-many relationship between two tables, pretty much unusable in Lightswitch.

I don't have time to detail but I will post the links here for my furture reference.

The response from MSFT in this connect article says it's fault of WebAPI and there is a workaround:

https://connect.microsoft.com/VisualStudio/feedback/details/811515/lightswitch-html-client-2013-connected-with-odata-backend-related-object-navigation-property-only-displayed-on-first-occurence

Basically WebAPI OData creates two nav props for each relationship as an easy way to prevent the complexities of figuring out which two navprops in the model represent the same relationship.

The problem is described sucinctly here:

http://stackoverflow.com/questions/15828643/missing-inverse-property-in-asp-net-webapi-odata-metadata/15842033#15842033

In which it is written:

QUOTE

Suppose:

      public class Manufacturer
{
    public int Id { get; set; }
    public Product[] RawMaterials { get; set; }
    public Product[] Produces { get; set; }
}
      
public class Product
{
    public int Id { get; set; }
    public Manufacturer[] Producers { get; set; }
    public Manufacturer[] Consumers { get; set; }
}

It is not trivial to figure out that Maufacturer.RawMaterials and Product.Consumers should share the same relationship and Manufaturer.Produces and Product.Producers should share the same relationship. We chose not to do it because the clients that we know of don't make much out of this information.

ENDQUOTE

Unfortunately, Lightswitch is one client that does 'make much out of this information'.

There is a way to manually build the EDM model (metadata) to specify the associations, but I give up for today.

More info:

https://aspnetwebstack.codeplex.com/workitem/623

https://aspnetwebstack.codeplex.com/workitem/792

https://aspnetwebstack.codeplex.com/workitem/802

https://github.com/OData/WebApi/issues/27

Possible solutions:

http://blogs.msdn.com/b/odatateam/archive/2014/06/30/refer-other-models-when-constructing-edm-model.aspx

https://gist.github.com/raghuramn/5864013

https://gist.github.com/dariusclay/8673940 

PS...This is fixed by the new metadata format in ODataV4, but that doesn't help with LS.

Feb 13
WebAPI OData Part 4 - Expose OData V3

WebAPI OData Part 1 - Setup Sample database

WebAPI OData Part 2 - Setup Entity Framework

WebAPI OData Part 3 - Expose OData with RESTier

WebAPI OData Part 4 - Expose OData V3< You are here

WebAPI OData Part 5 - Hook it up to LightSwitch

In the last article, we exposed OData V4 using RESTier.  That was easy, now let's compare that to the experience of exposing OData V3 without RESTier.  Spoiler alert:  it's more work without RESTier, but since LightSwitch doesn't understand V4, it must be done.

In this article we will pick up where we left off and expose the same database, using the same EF models and context.  It boils down essentially to adding controllers and mapping another endpoint.

1) Right-click on Controllers > Add Controller > choose  Web API 2 OData Controller with actions, using Entity Framework.

2) Choose Addresses (ODataSample.Models) as the model, AdventureWorksContext for context > Next.

3) Repeat this for every Entity.  I know that's tedious.  This is precisely where RESTier is helpful - it allows only one controller for the entire Domain.

4) Each Controller file has a commented section describing how to register the resopective OData endpoint in WebAPI.config.  Open Person.cs and copy the code out of this commented section and paste it inside the register method of WebAPIConfig.cs.  Do this until all entities are included.  Your final WebAPIConfig.cs should look like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;

using System.Web.Http.OData.Builder;
using System.Web.Http.OData.Batch;
using System.Web.Http.OData.Extensions;
using ODataSample.Models;

namespace ODataSample
{
    using Microsoft.Restier.WebApi;
    using Microsoft.Restier.WebApi.Batch;
    using Controllers;

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.MapHttpAttributeRoutes();
            RegisterAdventureWorksV4(config, GlobalConfiguration.DefaultServer);
            RegisterAdventureWorksV3(config, GlobalConfiguration.DefaultServer);
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }

        public static async void RegisterAdventureWorksV4(HttpConfiguration config, HttpServer server)
        {
            await config.MapODataDomainRoute(
               "AdventureWorksV4", "AdventureWorks/v4",
                new ODataDomainBatchHandler(server));
        }
        /**/
        public static void RegisterAdventureWorksV3(HttpConfiguration config, HttpServer server)
        {
            ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
            builder.EntitySet
      
      
("Addresses"); builder.EntitySet("AddressTypes"); builder.EntitySet("BusinessEntities"); builder.EntitySet("BusinessEntityContacts"); builder.EntitySet("BusinessEntityAddresses"); builder.EntitySet("EmailAddresses"); builder.EntitySet("People"); builder.EntitySet("PersonPhones"); builder.EntitySet("PhoneNumberTypes"); builder.EntitySet("Passwords"); builder.EntitySet("StateProvinces"); builder.EntitySet("CountryRegions"); builder.EntitySet("ContactTypes"); //HttpServer server = new HttpServer(config); config.Routes.MapODataServiceRoute("AdventureWorksV3", "AdventureWorks/v3", builder.GetEdmModel(), new DefaultODataBatchHandler(server)); //config.Routes.MapODataServiceRoute("odata", "odata", builder.GetEdmModel()); } } }

I chose to put it into a separate method called RegisterAdventureWorksV3 which is called from Register method.

My blog is crap so get the code from here:

https://gist.github.com/joshbooker/5ff03b12e77ff93bea14#file-part4-webapiconfig-cs

Done now F5. 

We now get OData V3 at this URI:

  http://localhost:<port>/adventureworks/v3

and ODataV4 at this URI:

 http://localhost:<port>/adventureworks/v4

Next we'll hook up to LightSwitch and give it a whirl.   

Feb 11
WebAPI OData Part 3 - OData with RESTier in 1-2-3

WebAPI OData Part 1 - Setup Sample database

WebAPI OData Part 2 - Setup Entity Framework

WebAPI OData Part 3 - Expose OData with RESTier< You are here

WebAPI OData Part 4 - Expose OData V3

There are two flavors of OData in ASP.Net WebAPI.  ODataV3 & V4.  The cool thing is they can both be side by side in the same project - using the same entity model because the Namespaces are different. (System.Web.Odata for V4 and System.Web.Http.Odata for V3) NOTE TO SELF: did I get these names correct?.

 

Let's start with V4 since it's new and awesome and all the cool kids are doing it...plus it's easier with RESTier and there's some more stuff I want to try after like OData Client Code Generator.

This is what we've been waiting for...we're basically  gonna follow this tutorial ...here goes: 

0) INSTALL:

 Install RESTier by running this in Nuget Package Manager Console:

 Install-Package Microsoft.Restier -Pre

1) DOMAIN:

Right-click Models folder > Add > Class > Call it AdventureWorksDomain.cs then paste in this code:

NOTE:  My crappy blog is a pain about code so please get the code from here instead:

https://gist.github.com/joshbooker/5ff03b12e77ff93bea14

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using ODataSample.Models;

namespace ODataSample.Controllers
{
    using Microsoft.Restier.EntityFramework;
    public class AdventureWorksDomain : DbDomain

    {
        public AdventureWorksContext Context
        {
            get { return DbContext; }
        }
    }
}
}

2) CONTROLLER:

 Right-click Controllers folder > Add > Controller > Call it AdventureWorksController.cs then paste in this code:

NOTE:  My crappy blog is a pain about code so please get the code from here instead:

https://gist.github.com/joshbooker/5ff03b12e77ff93bea14

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using ODataSample.Models;

namespace ODataSample.Controllers
{
using Microsoft.Restier.WebApi;
    public class AdventureWorksController : ODataDomainController
    {
        private AdventureWorksContext DbContext
    {
        get { return Domain.Context;}
    }
    }
}

3) ROUTE:

Open the file App_Start/WebApiConfig.cs and replace with this code:

NOTE:  My crappy blog is a pain about code so please get the code from here instead:

https://gist.github.com/joshbooker/5ff03b12e77ff93bea14

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web.Http;
using Microsoft.Owin.Security.OAuth;
using Newtonsoft.Json.Serialization;

namespace ODataSample
{
    using Microsoft.Restier.WebApi;
    using Microsoft.Restier.WebApi.Batch;
    using Controllers;

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.MapHttpAttributeRoutes();
            RegisterAdventureWorks(config, GlobalConfiguration.DefaultServer);
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}"
                defaults: new { id = RouteParameter.Optional }
            );
        }

        public static async void RegisterAdventureWorks(HttpConfiguration config, HttpServer server)
        {
            await config.MapODataDomainRoute(
               "AdventureWorksV4","AdventureWorks/V4",
                new ODataDomainBatchHandler(server));
        }
    }
}

DoneF5 and try it out.

Send some requests:

http://localhost:<port>/adventureworks/v4/People?$top(10) 

http://localhost:<port>/adventureworks/v4/People?$filter=(BusinessEntityID%20eq%201)

http://localhost:<port>/adventureworks/v4/People(1)/?$expand=EmailAddresses

Feb 11
WebAPI OData - Part 2 - Setup Entity Framework

WebAPI OData Part 1 - Setup Sample database

WebAPI OData Part 2 - Setup Entity Framework< You are here

WebAPI OData Part 3 - Expose OData with RESTier

WebAPI OData Part 4 - Expose OData V3

Short and sweet.

Create Entity Model 

1) In Nuget Package Manager Console run:

Install-Package EntityFramework 

2) Right-click on Models folder > Add > ADO.Net Entity Data Model​

3) Name it AdventureWorksContext and choose Code First from Database > Next

4)  Wow the EF wiz has my db & connection all filled for me - That was easy.  Type AdventureWorksContext for Web.config name at the bottom > Next.

5) Check off Tables > Finish

Now you should have a class for each table in Models folder and one inheriting DbContext which should be called AdventureWorksContext. 

Enable Migrations 

Now lets enable migrations so change to the model will update the database:

1) in nuget package manager console run this:

Enable-Migrations 

2) Now this:

Add-Migration InitialCreate

This will create and display a DbMigration called InitialCreate. 

3) Now comment out all the code inside the Up() method.  This will allow us to ‘apply’ the migration to the local database without trying to recreate all the tables etc. that already exist.

4) In npm console run this:

Update-Database

5) Now un-comment the code in the Up() method.  This means that when this migration is applied to future databases (when we deploy, for exapmple), the schema that already existed in the local database will be created by migrations.

Not too hard, was it?

Want screen shots?

Try these:

https://msdn.microsoft.com/en-us/data/jj200620

https://msdn.microsoft.com/en-us/data/dn579398 

Test Migrations:

1) Open Person.cs and add a field 'PetName'

[StringLength(50)]

 

public string PetName { get; set; }

2) in npm console run this:

 Add-Migration Person.PetName

3) Now this:

 Update-Database -Verbose

4) do you see the column in Person table of Server Explorer?  Sweet!

Now let's back out: 

 1) in npm console run this:

Update-Database –TargetMigration: InitialCreate -Verbose

This reverted the database schema back to the InitialCreate state.  

2) Is PetName gone from Table?  Super Sweet. 

 

3) You can delete the <SomeDate>_Person.PetName.cs file from Migrations folder and remove the column code from Person.cs.

4) Let's just prove that migration is gone-gone.  Try this:

Update-Database -Verbose

Success =

No pending explicit migrations.

I think we're getting the hang of this migration stuff! 

Up Next > we're ready to try an OData endpoint.

Feb 11
WebAPI OData Part 1 - Setup Sample database

WebAPI OData Part 1 - Setup Sample database< You are here   

WebAPI OData Part 2 - Setup Entity Framework

WebAPI OData Part 3 - Expose OData with RESTier

WebAPI OData Part 4 - Expose OData V3

1) Download Adventure Works 2014 OLTP Script.zip  and extract all.

2) In VS2013 Create New ASP.Net Web Application - choose Empty Template abd check off WebAPI.

3) In Solution Expolorer, right-click on App_Data > Add > Existing Item.

4) Choose instawdb.sql in extracted script location from step 1.  Copy or make note of the full script directory path while you're there. 

5) Open instawdb.sql from App_Data.  Change Connection to (localdb)\V11.0 and click SQL CMD Mode button.

6) Change :setvar lines 'SqlSamplesSourceDataPath' to the full path where you extracted the script in step 1.  If you copied the path in step 4 then just paste.  Leave trailing slash at the end of the path.  Do the same for SqlSamplesDatabasePath, but set that to the full path of your porjects App_Data folder - again with trailing slash.

7) Find 'Create Full Text catalog and indexes'  then comment that section out until the next section labelled 'Create Foreign key constraints'.  (full text not supported on users instance error)

8) Find 'uspSearchCandidateResumes'  and comment that stored procedure out.  (full text search not supported)

9) Execute the script - hope to see 'Query Executed successfully' - Mine took under 3 min.

10) App_Data > Add > Existing Item > Select 'AdventureWorks2014_Data.mdf' in project's App_Data directory.

TEST)  In Server Explorer > Data Contections: Confirm you see AdventureWorks2014_Data.mdf and can open tables.

That wasn't too bad.

Dec 04
HTML Client Computed Properties

It would be nice and fairly simple for MS to add Computed Properties to the HTML Client.  In fact if they'd just include them in the model then we could do it ourselves. 

Rather than hold our breath, let's make it happen.

Here's one way to make Computed Properties easy - and it requires zero change to msls.js.

1) Add an optional property to your entity.  I have Customer and added a property called 'CSZ' - required=false.

2) In the entity designer select HTML perspective and choose Write Code | Created.  This adds the entity.js client side script. (customer.lsml.js in my case)

3) Now write _Compute methods in your entity.js file (Customer.lsml.js for example) like so:

 

 

myapp.Customer.created = function (entity) {
    // Write code here.

};

myapp.Customer.CSZ_Compute = function (entity, result) {
    // Write code here.
    var Me = entity;
    result = Me.City + ', ' + Me.State + "  " + Me.Zip;
    return result;
};

 

Notice how similar this is to SL compute methods.

4) Create a new script called OdataReadPatch.js in your scripts folder and paste in the this code. Also add a reference to this script in your default.htm after all other script refs.

 

/*
This script patches the OData read method 
enabling us to inject operations before the results return to the LS runtime
*/
var origJsonReadFunc = OData.jsonHandler.read;
OData.jsonHandler.read = function (response, context) {
    var result = origJsonReadFunc.call(OData.jsonHandler, response, context);
    var data = response.data, results = data.results;
    if (results) {
        results.forEach(function (entity) {
            //do stuff to each entity here

            //call function to add Computed properties
            myapp.addComputedProperties.call(this, entity);

        });
    }

    return result;
};

myapp.addComputedProperties = function (entity) {

    //get entity name  ie: "LightSwitch.Customer" = "Customer"
    var entityType = entity.__metadata.type.split(".").pop();

    //get the entity class - this object contains all methods defined in Customer.lsml.js
    var entityClass = myapp[entityType]

    //build an array of property names from '_Compute' methods
    var properties = [];
    for (var p in entityClass) {
        if (typeof entityClass[p] === "function" && p.indexOf("_Compute") > 0) {
            prop = { name: p.split("_")[0], type: String };
            properties.push(prop);
        }
    };
    //console.log(properties);

    //add the computed prop to this entity by calling the _Compute method
    if (properties) {
        properties.forEach(function (entry) {
            var entryName = entry.name;
            var computeMethod = entityClass[entryName + "_Compute"];
            entity[entryName] = computeMethod.call(this, entity);
        });
    }
};

or download OdataReadPatch.js from here:

https://gist.github.com/joshbooker/ddb4b07683e32a068d5d

Done!

 

Now just add the property to a browse screen and it works.

After this is setup, adding new computed props is as simple as adding a _Compute methods.

The trick is the Odata patch which allows us to inject an operation to add the properties to each entity before the results are returned to LS runtime.

More info:

The idea was to make HTML Client computed properties nearly the same as SL.  My understanding is that the _compute methods in SL is on the client - or at least both client and server.  The missing part on the HTML client was until they added the entity_created method, there was no client side table code to mirror that in the .net client.

LS defines entities using the generated model file data.js which cannot be changed and computed properties are not included.  As a result, we typically use existing entry points to add them such as _render & _postRender.  These are specific to code in a single screen and for a single contentItem on that screen.  Thus there is a ton of code duplication for a computed prop that appears on many screens or many times on a single screen.  Also, to keep the computed property current, dataBind code is needed.  For a simple computation like 'CSZ' above, which references 3 other properties (City, State & Zip),  at least 3 dataBinds are needed.  Need to change a calculation? - Make sure to do it here and here and here and there and there, you get the picture.  With this solution, the calculated property is added to the data - no the screen, computation code is written in one place and dataBind is automatic.

How does it work?

This solution adds properties to the entityData returned from each Odata Read call.  With these props present, LS builds the entities and the properties are majically added to the Entity Details just as though they were in the model to begin with.  So LS runtime doesn't appear to 'know' they are any different that another field in the data.

The collection of computed properties to be added is derived from looking directly at the client side entity code (<entity>.lsml.js) and finding all methods ending in '_Compute'.  It is assumed that those methods will accept entityData and return the computed result string.  The name of the property is the portion of the methid name before _Compute ( ie: 'myapp.Customer.CSZ_Compute(entity)' adds a computed property called 'CSZ' to all Customer entities).  Therefore all you have to do to add a computed prop is add another _Compute method to your entity code.  The property is added even if you don't add one of the same name in the entity designer and is therefore available for _render and dataBind manually.

Optionally, If you do add a property by the same name to the entity designer, you can use it on screens and LS will render and bind the computed value on screens automatically - no _render/dataBind necessary.  That part was a shocker to me.

Because this computes the props on every OData call, the values are recomputed quite often.  There's barely a need for dependency changes listeners to auto-recompute like SL does.

To summarize,

  • You create these computed props in one step,
  • The computation exists in one and only one place. 
  • By optionally adding a prop to the model and views LS will do the dataBinding for you.
  • They re-compute with every Odata call
  • Zero change to msls.js.

I think this hook may be the ticket to crack the data layer for a lot of other benefits.

Have a greeat day!

May 19
LightSwitch Dynamic Connection Strings Now Supported

Yes that's right...no really...not kidding.

Visual Studio 2013 - Update 2 released last week and Eric Erhardt dropped a hint today here saying the following:

In the latest version of LightSwitch (VS 2013 Update 2) a new partial method was introduced on DataService classes that connect to a backing database. This method's signature is partial void <DataSourceName>_InitializingConnection(DatabaseConnectionState state). The state object has a ConnectionString property, that allows you to set the connection string to whatever you choose at runtime. This allows you to switch database instances, or username/password, etc.

This should be able to provide as much flexibility you need.  From this method, you can grab the current Application.User, and switch off of it.  Or you can call into SharePoint to get more information. 

That sounded specific to databases so I asked about sharepoint and he said this:

This method is only available for "Database" connections (either the ApplicationData or another attached Database data source). For OData service and SharePoint data sources, the new partial methods are:

partial void DeveloperData_SendingRequest(ODataSendingState state)

 

partial void DeveloperData_ReceivedResponse(ODataReceivedState state)

These methods allow you to intercept/inspect the outgoing request to the backend OData service, and inspect the incoming response from the backend service.

Just an FYI - none of these new methods show up in the Write Code drop down in the designers.  To get to them, you need to open the code file in the Server project (or generate it by selecting SaveChanges_CanExecute from the Write Code drop down, and then deleting the generated method stub).  Once in the xxxDataService class, type "partial " and from the auto-complete box, select the corresponding partial method.  In VB, you can select the partial method from the drop down at the top of the code file.

Wow, this is HUGE!

Look out for a Team blog about this and perhaps several from community members who'll prolly drop everything to try it out!

 Thank you LS Team, you're the best.

UPDATE:  2014-05 JUNE I still havn't had time to try this but Michael Washington has a nice post which includes this info here:

http://lightswitchhelpwebsite.com/Blog/tabid/61/EntryId/3264/Creating-A-LightSwitch-SharePoint-2013-Multi-Tenant-Provider-Hosted-Application.aspx

UPDATE:  2014-03 JULY I still havn't had time to blog about this, but OData URI cannot be changed in the new _Sending Request method.  This merans OData connections are not dynamic at runtime.

Eric's Comments in the above mentioned thread:

"Unfortunately changing the Request URI isn't something you can do during the OData service _SendingRequest method.  We need to expose another method - BuildingRequest - that would allow you to change the Request URI."

"The underlying design of the _SendingRequest method is that we use the OData Client's DataServiceContext.SendingRequest2 event, and whenever that is raised, we call the _SendingRequest in your code.  It was realized too late in the release cycle that the SendingRequest2 event didn't allow the URI to be changed. In order to do that in the OData client DataServiceContext, we need to subscribe to the BuildingRequest event."

IMPORTANT:  The popular long-standing Uservoice suggestion for this was incorrectly marked completed and unfoirtunatly  I was asked to create a new one specific to OData.  Your votes and sharing is very much appreciated:

PLEASE VOTE:

Allow Lightswitch OData connection URI to be changes dynamically at runtime 

May 06
msls Exposed: Declarative Binding

 

msls.js is amazing and we only see a fraction of it.  Recently I tried an experiment to see more at runtime by exposing everything in the msls namespace.  This is the first in perhaps several posts on what new morsels are found under the covers.

It's been enlightening to learn from some js masters in the LS forums.  Recently it's been around declarative data binding in the HTML Client.  I wrote a post with one way to do this and forums user LittleBobbyTables shared some great insights on how to do it better using WinJS binding with HTML templates and he said:

One goal of mine in posting these samples is to encourage developers smarter than me (read: all of you guys seeing this) into possibly experimenting on their own, then sharing their discoveries here on the forum. 

While I'm definitely not smarter, I have been experimenting so I'll share...

The ContentControl

There is a control in msls called ContentControl which does declarative binding.  msls uses it internally to render headers and commands bars and-the-like from html templates.  Some of the code for this control can be seen in my post from Jan 29th in the above referenced forum thread.

Two problems: 

1) You cannot create this type of control in the designer and

2) msls.ui.controls namespace is not 'exposed'.

Well as an experiment, I exposed everything, then tried this, in a custom control bound to screen.Customer:

(UPDATE: I'm having issues with posting this code on the blog, silly editor keeps altering my code  please get the code from here:  https://gist.github.com/joshbooker/ed30e98c62534a1529f6 )

 

myapp.AddEditCustomer.Customer2_render = function (element, contentItem) {
    // Write code here.
    thatTemplate = '
      
'; thatControl = new msls.ui.controls.ContentControl($(element)); thatControl.dataTemplate = thatTemplate; thatControl.data = contentItem.data; thatControl.render();

What do you know...it worked... I see Northwind contact name 'Maria Anders' with two-way binding!

It renders in the div.subControl a msls.ui.controls.TextBox with all the default props, bindings, etc.  Then it binds the text property to contentItem.data.ContactName. 

As you can see the .dataTemplate is declarative HTML. 
 

      
 
 
The ContentControl does the following:
  • Checks the template for elements having class="subControl" so that's required on everything that you want to bind declaratively
  • Checks for control attribute using the value to create & render a msls.ui.control[controlId] where controlId is the control type (Text, TextBox, ListView, etc.)
  • Looks for all attributes beginning with 'data-ls' using the value to set up binding (one-way binding, I think) relative to the .data property which is set to contentItem.data in this case. 

 

So the declarative binding here is between control.text and contentItem.data.ContactName ( or screen.Customer.ContactName - since the CustomControl is bound to screen.Customer.)      

 

 

I wish they'd expose more stuff, especially ui.controls.

Thanks to all communinty members for sharing your insights!

May 04
msls.js Exposed

The msls.js documentation By Soft Landing is such a great asset for the LS HTML Community.

Wouldn't it be nice to to see into internal msls objects at runtime?

Let's expose everthing with one line of code. 

1) Find this function 'addToNamespaceCore' in msls.js

2) add a line before the 'return' statement

msls_expose(path, ns);

That's all.  The function should look like this:

 

    function addToNamespaceCore(parent, path, members) {
        var standardMembers = processMembers(members),
            ns = WinJS.Namespace.defineWithParent(
                parent, path, standardMembers);
        defineExtendedMembers(ns, members);
        //CUSTOM: expose everything
        msls_expose(path, ns);
        return ns;
    }

 Now let's look at msls object in the watch pane. 

Interesting... among lots of other objects, we have all the msls.ui.controls exposed.

exposed1.JPG Hmm....I wonder if these could be used to render custom controls. (?)

Also check out msls.services.modelService.  This should be fun.

1 - 10Next