Migration from Lightswitch

Thanks guys.

Perhaps my userID example isn’t the best to illustrate the suggestion.

I understand the ability to authorize on a controller level but I’m talking about the need to FILTER an entity set server-side based on a parameter (not necessarily based on user). So in this case the user would be authorized to access the controller, but it would apply a .Where condition as in the above code.

Another example, say I have a multi-tenant app sharing the same database, tenancy is a field in the Customer Table and want Customer Controller to return only those records assigned to a certain tenancy.

public partial class CustomersController
{
    partial void OnCustomersRead(tenantID string, ref IQueryable<Category> items)
    {
      items = items.Where(i => i.tenantID = tenantID);
    }
}

The missing bit is how to get the generated code to include the tenantID parameter. If radzen had the ability to ‘model’ queries on top of entity sets we could achieve this.

Thanks for listening.

Thank you Joshbooker - I think you completely understand what I need to achieve!

@joshbooker I don’t think OData (which we use exclusively) allows for passing custom parameters to entity query methods like that. Can you use the standard $filter operation to achieve the same? You can keep the tenant id as a page property and then call all methods with $filter=TenantID eq ${tenantID}

@korchev thanks for your reply. $filter is insecure because a crafty user could hit the endpoint without a filter. The idea is to have the filter happen on the server transparent to the client.

Thus the suggestion to enhance radzen to enable modeling of simple queries at designtime. The query would generate a parameterized get method in the server side controller for the base entity class. Similar to how you gen for stored procs. Except a query is updatable because it’s an extension method of the base entity get method.

This would also make the middle tier more capable of being THE place for server side business logic.

@joshbooker how did LightSwitch handle this case? Where did it get the input parameters e.g. TenantID? Are such parameters hardcoded values or retrieved from the database somehow? Thanks!

@korchev thanks for your reply. In LightSwitch queries could be modeled on top of entities having Filters, Sorts and Parameters. At design time a filter is added by selecting the property, comparison operator (eq, gt, contains, etc), the 'value type' and the value.

The value type can be either Literal, Parameter, Property, or Global.

  • Literal value is hard-coded
  • Parameter value generates the getter method with a parameter arg (as-in TenanID above)
  • Property value compares one property to another (ie: Customer.ModifiedOn >= Customer.CreatedOn)
  • Global is a seldom used option similar to Parameter but global in scope.

image

On the server side, three 'queries' are generated for every entity. All(), Single(id), SingleOrDefault(). Queries are composable so every query including singletons are composed on top of the All() query.

All modeled queries take a filter expression of the following type which evaluates to a boolean.

System.Linq.Expressions.Expression<System.Func<Customer, bool>>

Which is interpreted in the query method like so .Where(customer.tenantID == myTenantParam);

When you design a page based on a query you can optionally bind the parameter value to screen properties such as an input box value.

thanks for listening

Thank you for explaining this! I have a few more questions:

  • Are those parameters mandatory? What happens if the user doesn’t specify a value in the input box?
  • Are those parameters passed from the client-side to the server side?

I am asking because if the value is not mandatory or passed from the client to the server-side a crafty user can still hit the endpoint without a filter. Or pass a different tenant id and see data for another user. This is probably not the case but it isn’t clear to me how LightSwitch queries mitigate that security risk. Getting the tenant id server-side based on the current user looks most secure.

Could you please clarify?

In LS, the query may have fixed parameters which the end user has no control, hence the ability to give the user access to specific rows rather than the whole table contents.

I will get a screenshot to help explain.

@korchev. Literal parameters are not passed the values are specified at designtime and generated hardcoded serverside. CurrentUser and tenantID would be examples of a Global parameter the value of which is known server side so it is not passed from the client. So generated code would include getting that value similar to the current user code by @enchev above. Named parameters are passed from the client and can be made optional at designtime. When optional, the generated code implements OR conditions to return results when a param value is missing.

As can be seen above, in LS, I created a fixed parameter query (note: on the linked table - clientdetail) called OCSdata. There is no input required by the end user. The screen creation wizard uses this query, which in turn, retrieves the rows from the table which match the parameter thereby restricting the user to seeing only those records. The user cannot alter the query parameter.

Whilst I see the usefulness of linking the query to the userid, I build different apps (layout, functionality, customer branding etc) based upon the users needs. Therefore, I restrict access to the app to the userid instead.

@korchev I like the addition of parsing bound functions as described here

https://www.radzen.com/documentation/odata/#metadata

Would this be a step in the direction of adding designtime queries? Now if there was a way to design the filters and then generate the functions.

Thanks

@joshbooker This was needed to expose as much of the MSGraph API surface as possible. However it would definitely be useful for “designed” queries. We are already using something similar when exposing MSSQL stored procedures.

Would it make sense to add a refresh data source operation which, instead of full infer from sql, would get the OData meta from the dot net backend. So we could add functions in controller code and then refresh to get them in the designer meta?

Yes, this makes sense! A user could create an OData function/action and Radzen should be able to “see” it later. Registering the OData function needs a few “escape hatches” but is definitely possible. Adding it to the backlog.

Thanks. I’d say the ability to model simple queries at designtime would be preferred so it doesn’t regenerate upon full infer from Sql but a refresh from OData meta would be a nice interim option.

Besides designing database queries what are the other missing features that LightSwitch developers would could ask for?

A big one is the ability to define relationships with full navagation properties supporting $expand etc across two different datasources. Like relate customers in CRM to cases in zendesk for example.

This is a potential benefit of generating a single server tier for every app. The server is like a middle tier that mashes any number of disparate data sources exposing them as one OData service and making a place for common serverside business logic.

In that way, LS was useful for generating the OData service and backend logic alone even without the UI.

What was always missing from LS, and contributed to its downfall IMHO, was the fact that the build and gen templates were a black box - never made available to users. Radzen’s simplicity in the use of swagger for meta and .ejs for generation could change that and open your awesome tool to community contribution making the horizon endless. Thanks for listening.

Back in the day, the LS community discussed vNext wish list. I’ll review some old posts on forums and share them here.

That would be awesome @joshbooker! Thanks as always.

Atanas,

Reviewing some old posts this morning. My RadZen enthusiasm has been awakened as has my LightSwitch nostalgia.

Regarding above, does Radzen do:

  • design-time query builder
  • server-side parameter queries
  • single back-end over mutilple data sources
  • cross-data source relationships
  • refresh data source operation from the dot net backend, instead of full infer from sql

Thanks,
Josh

1 Like