BookmarkSubscribeRSS Feed
🔒 This topic is solved and locked. Need further help from the community? Please sign in and ask a new question.
JohnJPS
Quartz | Level 8

We're in the process of developing our first model using SAS, and we know we'll want to score it via a web service, presumably done RESTfully, utilizing json for the calls.  Another team will be consuming the webservice using clients developed in C#.  They were asking if we could generate a Swagger contract based on our eventual web service.

 

Thus I was wondering if anyone else has implemented C# clients of RESTful services with lots of (100 - 200'ish) parameters involved.

 

Reason for asking: for instance, a C# client can target a SOAP/XML web service, and during development in Visual Studio, it's pretty much "right-click, point at the URL", and it generates base classes automatically, which is pretty handy to say the least.  We'd like to acheive the same sort of easy implementation with our SAS REST service.  Swagger preferred, but any comments appreciated.

1 ACCEPTED SOLUTION

Accepted Solutions
JohnJPS
Quartz | Level 8

Just an FYI on this... I was able to generate a C# client to call a MAS REST API (as well as a SASBIWS json stored process).  MAS appears to be quite a bit faster for scoring a simple sample model but that's irrelevant to this specific topic.

 

For MAS, I first generate a ticket; (by HTTP Post to: https://SERVER/SASLogon/rest/VERSION/tickets with UID and PWD parameters... which returns a pre-authenticated URL in a "Location" header; calling that via another http post returns a ticket that can temporarily be used for subsequent calls).

 

So, I do that and set a breakpoint in Visual Studio order to grab the ticket... then tried NSwagStudio, looking to auto-generate implementation classes... so, basically, targeting this in NSwagStudio...

https://SERVER/SASMicroAnalyticService/rest/modules/MODEL/steps/execute?ticket=TICKET

(where SERVER = my midtier server, and MODEL = the name of what I deployed to MAS from Decision Builder)...

 

... I was then able to pull a bunch of .json into Swagger... but, unfortunately, it is not in a format for which Swagger can automatically parse and generate C# client classes.

 

However, it does pretty print ths json, and based on that it was pretty straight-forward to manually implement a generic "ScoringOutput" class, and to map it using a combination of RESTSharp (to make the calls to MAS) and Newtonsoft Json.Net, for parsing the resulting json.

 

Here's what I did:

The "SAS_ScoringOutput" class:

    class SAS_ScoringOutput
    {
        public String moduleId = "";
        public String moduleName = "";
        public String stepId = "";
        public IList<SAS_Mappings.SAS_Links> links = new List<SAS_Mappings.SAS_Links>();
        public Int64 version = 0;
        public IList<SAS_Mappings.SAS_NameValuePair> output = new List<SAS_Mappings.SAS_NameValuePair>();
    }

which uses two other helper classes...

    class SAS_Links
    {
        public String method;
        public String rel;
        public String href;
        public String uri;
        public String type;
    }

...and...

    class SAS_NameValuePair
    {
        public String name = "";
        public String value = "";
        public SAS_NameValuePair(String pName, String pValue)
        {
            name = pName;
            value = pValue;
        }
    }

Given all that, and references to both RESTSharp and Json.Net, and a valid ticket... the following should work:

private static SAS_ScoringOutput CallModelScoring(String sasTicket)
{
    SAS_ScoringOutput jsonRsp = null;

// 1. build the request StringBuilder jsonRequest = new StringBuilder("");
// build your json request here... system dependent
IRestClient rstClient1 = new RestClient(); rstClient1.BaseUrl = new Uri("https://SERVER/SASMicroAnalyticService/rest/modules/MODEL/steps/execute?ticket=" + sasTicket); rstClient1.CookieContainer = new System.Net.CookieContainer(); IRestRequest rstReq1 = new RestSharp.RestRequest(); rstReq1.Method = Method.POST; rstReq1.AddHeader("Accept", "*/*"); rstReq1.Parameters.Clear(); rstReq1.AddParameter( "application/json", jsonRequest.ToString(), ParameterType.RequestBody); // 2. Make the Call IRestResponse rstRsp1 = rstClient1.Execute(rstReq1); // 3. Process Results Newtonsoft.Json.Linq.JObject jo = Newtonsoft.Json.Linq.JObject.Parse(rstRsp1.Content); jsonRsp = new SAS_ScoringOutput(); jsonRsp.moduleId = jo.SelectToken("moduleId").ToString(); jsonRsp.moduleName = jo.SelectToken("moduleName").ToString(); jsonRsp.stepId = jo.SelectToken("stepId").ToString(); jsonRsp.version = long.Parse(jo.SelectToken("version").ToString()); foreach (Newtonsoft.Json.Linq.JToken jt in jo.SelectToken("output").Children()) { jsonRsp.output.Add(new SAS_NameValuePair(jt.SelectToken("name").ToString(), jt.SelectToken("value").ToString())); } return jsonRsp; }

There are C# predicate tricks that could reduce the lines of code, and probably other code improvements that could be made, but you get the idea. Also, my names/values are always String... for a model that returns int/double/boolean/date type parameters, you'll have to do the appropriate conversions when mapping your SAS_ScoringOutput class back into the consuming application code layers.

 

Anyway, hope that helps, if anyone's working on something similar.

 

View solution in original post

7 REPLIES 7
BrettWujek
SAS Employee

Hey John - One way to do this would be by registering your SAS code as a stored process and accessing it through the REST API offered in the BI Web Services interface. I did a little work with this in the past, but only with python, Java, and R.  But translating to C# should hopefully not be too difficult.  Just take a look at the attached doc and see if it helps you out at all.

 

I'm assuming this is SAS 9.4.  If you have SAS Viya then this would be even easier as it provides a REST API directly (along with APIs for other languages).

 

Hope this helps.

Brett


Register today and join us virtually on June 16!
sasglobalforum.com | #SASGF

View now: on-demand content for SAS users

ccaulkins91
Obsidian | Level 7
couldn't help but notice that you might be missing a reference to the number of columns and how less is more.
Data Dugger
ccaulkins9
Pyrite | Level 9
Try feature selection, first -
then target whatever you want
e-SAS regards,

BrettWujek
SAS Employee

I think the question is more about the REST API right?

 

But beyond that, yes - you should consider feature selection to reduce the number of parameters you need to pass to your web service and model with.


Register today and join us virtually on June 16!
sasglobalforum.com | #SASGF

View now: on-demand content for SAS users

JohnJPS
Quartz | Level 8

 

I'm only in charge of operationalization, so the entire model, including feature engineering/reduction is out of my hands.  I'm content with my ability to create the REST API, and I can generate a C# client to serve as a reference implementation, (thanks for the document, @BrettWujek - that helps).  But am asking about Swagger primilarily based on the same question being asked of me by my eventual C# consumers: they want something that will allow them to auto-generate their consumption classes and then worry only about mapping their core layer classes to the consumption layer classes.

 

Some browsing shows that others may have utilized Swagger to hit the API and generate what they need automatically; (based on bug reports at Swaggers' github site).  I'll give this a try and report back on how it goes.

 

Thanks for the feedback!

 

 

JohnJPS
Quartz | Level 8

Just an FYI on this... I was able to generate a C# client to call a MAS REST API (as well as a SASBIWS json stored process).  MAS appears to be quite a bit faster for scoring a simple sample model but that's irrelevant to this specific topic.

 

For MAS, I first generate a ticket; (by HTTP Post to: https://SERVER/SASLogon/rest/VERSION/tickets with UID and PWD parameters... which returns a pre-authenticated URL in a "Location" header; calling that via another http post returns a ticket that can temporarily be used for subsequent calls).

 

So, I do that and set a breakpoint in Visual Studio order to grab the ticket... then tried NSwagStudio, looking to auto-generate implementation classes... so, basically, targeting this in NSwagStudio...

https://SERVER/SASMicroAnalyticService/rest/modules/MODEL/steps/execute?ticket=TICKET

(where SERVER = my midtier server, and MODEL = the name of what I deployed to MAS from Decision Builder)...

 

... I was then able to pull a bunch of .json into Swagger... but, unfortunately, it is not in a format for which Swagger can automatically parse and generate C# client classes.

 

However, it does pretty print ths json, and based on that it was pretty straight-forward to manually implement a generic "ScoringOutput" class, and to map it using a combination of RESTSharp (to make the calls to MAS) and Newtonsoft Json.Net, for parsing the resulting json.

 

Here's what I did:

The "SAS_ScoringOutput" class:

    class SAS_ScoringOutput
    {
        public String moduleId = "";
        public String moduleName = "";
        public String stepId = "";
        public IList<SAS_Mappings.SAS_Links> links = new List<SAS_Mappings.SAS_Links>();
        public Int64 version = 0;
        public IList<SAS_Mappings.SAS_NameValuePair> output = new List<SAS_Mappings.SAS_NameValuePair>();
    }

which uses two other helper classes...

    class SAS_Links
    {
        public String method;
        public String rel;
        public String href;
        public String uri;
        public String type;
    }

...and...

    class SAS_NameValuePair
    {
        public String name = "";
        public String value = "";
        public SAS_NameValuePair(String pName, String pValue)
        {
            name = pName;
            value = pValue;
        }
    }

Given all that, and references to both RESTSharp and Json.Net, and a valid ticket... the following should work:

private static SAS_ScoringOutput CallModelScoring(String sasTicket)
{
    SAS_ScoringOutput jsonRsp = null;

// 1. build the request StringBuilder jsonRequest = new StringBuilder("");
// build your json request here... system dependent
IRestClient rstClient1 = new RestClient(); rstClient1.BaseUrl = new Uri("https://SERVER/SASMicroAnalyticService/rest/modules/MODEL/steps/execute?ticket=" + sasTicket); rstClient1.CookieContainer = new System.Net.CookieContainer(); IRestRequest rstReq1 = new RestSharp.RestRequest(); rstReq1.Method = Method.POST; rstReq1.AddHeader("Accept", "*/*"); rstReq1.Parameters.Clear(); rstReq1.AddParameter( "application/json", jsonRequest.ToString(), ParameterType.RequestBody); // 2. Make the Call IRestResponse rstRsp1 = rstClient1.Execute(rstReq1); // 3. Process Results Newtonsoft.Json.Linq.JObject jo = Newtonsoft.Json.Linq.JObject.Parse(rstRsp1.Content); jsonRsp = new SAS_ScoringOutput(); jsonRsp.moduleId = jo.SelectToken("moduleId").ToString(); jsonRsp.moduleName = jo.SelectToken("moduleName").ToString(); jsonRsp.stepId = jo.SelectToken("stepId").ToString(); jsonRsp.version = long.Parse(jo.SelectToken("version").ToString()); foreach (Newtonsoft.Json.Linq.JToken jt in jo.SelectToken("output").Children()) { jsonRsp.output.Add(new SAS_NameValuePair(jt.SelectToken("name").ToString(), jt.SelectToken("value").ToString())); } return jsonRsp; }

There are C# predicate tricks that could reduce the lines of code, and probably other code improvements that could be made, but you get the idea. Also, my names/values are always String... for a model that returns int/double/boolean/date type parameters, you'll have to do the appropriate conversions when mapping your SAS_ScoringOutput class back into the consuming application code layers.

 

Anyway, hope that helps, if anyone's working on something similar.

 

sas-innovate-2024.png

Don't miss out on SAS Innovate - Register now for the FREE Livestream!

Can't make it to Vegas? No problem! Watch our general sessions LIVE or on-demand starting April 17th. Hear from SAS execs, best-selling author Adam Grant, Hot Ones host Sean Evans, top tech journalist Kara Swisher, AI expert Cassie Kozyrkov, and the mind-blowing dance crew iLuminate! Plus, get access to over 20 breakout sessions.

 

Register now!

How to choose a machine learning algorithm

Use this tutorial as a handy guide to weigh the pros and cons of these commonly used machine learning algorithms.

Find more tutorials on the SAS Users YouTube channel.

Discussion stats
  • 7 replies
  • 2924 views
  • 10 likes
  • 4 in conversation