LightSwitch with Oracle Data Source (part 2)

External login and global parameters

In the previous part I mentioned I’m working on existing database, changing the application administration. One of the issues I have to solve is using  the existing authentication. LightSwith has build in authentication, but it uses .NET Role and Membership providers and they are not solution we choose for Oracle database when we started the project long time ago.

This post is about implementing external authentication in both Silverlight and HTML LightSwitch clients. Also I will implement some roles and some data filtering connected with the existing authentication used in this project.

There is one condition – the project have to be converted to the new version, because in the old LightSwitch you don’t have access to default.html file.

For implementing a scenario like this you have to disable the authentication in LightSwitch from project properties.

The next step is not to switch into File View mode and add Generic Handler. I place it in a sub-folder to keep the code clean.

p2 AddSessionService

The code in this file shall be designed according to your product structure.

(this is only an example of the file possible structure returning a hard-coded successful login response for test purpose only)

    public class SessionCheck : IHttpHandler
    {

        public void ProcessRequest(HttpContext context)
        {
            string sid = context.Request.QueryString["sid"];
            context.Response.ContentType = "text/plain";
            context.Response.Write(
                  "sid="+sid+";grid=100;userid=21;role=su;name=Ivan Vlaevski");
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }

After building the server side for authentication I needed to deliver the session ID into the Silverlight client. The best way to deliver values in Silverlight is through initParams in the HTML. For this to work change the default.html in the client. Maybe the final version will include default.aspx or else where I can generate the content, but for this example I will hard-code this too.

<div id=”silverlightControlHost”>
<object ID=SilverlightApplication data=”data:application/x-silverlight-2,” type=”application/x-silverlight-2″ width=”100%” height=”100%” >
<param name=”source” value=”Web/vSchool.Client.xap”/>
<param name=”onerror” value=”onSilverlightError” />
<param name=”background” value=”white” />
<param name=”minRuntimeVersion” value=”5.0.61118.0″ />
<param name=”autoUpgrade” value=”true” />
<param name=”initParams” value=”sid=1276527327723″ />
              <a href=”http://go.microsoft.com/fwlink/?LinkID=124807″ style=”text-decoration: none;”>
<img src=”http://go.microsoft.com/fwlink/?LinkId=108181″ alt=”Get Microsoft Silverlight” style=”border-style: none”/>
</a>
</object>
<iframe style=’visibility:hidden;height:0;width:0;border:0px’></iframe>
</div>

Next…

Switch back to Logical View and then open any screen. From Write Code  drop down select any ‘*_Run’ function and this will open the Application class.

In the Application class I add some global parameters:

public partial class Application
{        
        public string sid { get; set; }        
        public decimal grid { get; set; }
        public decimal userid { get; set; }
        public string role { get; set; }
        public string name { get; set; }

Initialization of the LightSwitch Application Client have to make a call to the Service I made in Server part for validating the session. The process of initialization have to wait until communication is completed.

private AutoResetEvent autoEvent = new AutoResetEvent(false);

partial void Application_Initialize()
{
    ///Session ID and Group ID set to -1 
    sid = "-1";
    grid = -1;

    ///Parse Init Parameters
    SilverlightHost host = new SilverlightHost();
    if (host.InitParams.Count > 0)
    {
        foreach (var c in host.InitParams)
        {
            if (c.Key == "sid") sid = c.Value;
        }
    }

    ///Invoke session validation
    Dispatchers.Main.BeginInvoke(() =>
    {
        ///Building query
        ///init params may hold a url to session validation too
        string strURL = string.Format("{0}{1}", 
                GetBaseAddress(), "SessionValidate/SessionCheck.ashx");
        Uri sessionValidateUrl = new Uri(strURL, UriKind.Absolute);
        UriBuilder ub = new UriBuilder(sessionValidateUrl);
        ub.Query = string.Format("{1}sid={0}", sid, 
                   string.IsNullOrEmpty(ub.Query) ? "" : ub.Query.Remove(0, 1) + "&");

        ///web client call
        WebClient client = new WebClient();
        client.DownloadStringCompleted += client_DownloadStringCompleted;
        client.DownloadStringAsync(ub.Uri, autoEvent);
    });

    ///Wait validation response for 30 sec
    if (autoEvent.WaitOne(new TimeSpan(0, 0, 30)))
    {
        //Success
    }
    else
    {
        //Timeout
        throw new Exception("Can't validate session");
    }

}

The WebClient completed event handler:

void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
    if (e.Error != null) throw e.Error;
    string[] _params = e.Result.Split(';');
    foreach (string s in _params)
    {
        string[] name_value = s.Split('=');
        switch (name_value[0])
        {
            case "sid": if (sid != name_value[1]) throw new Exception("Invalid Session"); break;
            case "grid": grid = Convert.ToDecimal(name_value[1]); break;
            case "userid": userid = Convert.ToDecimal(name_value[1]); break;
            case "role": role = name_value[1]; break;
            case "name": name = name_value[1]; break;
        }
    }
    ((AutoResetEvent)e.UserState).Set();
}

In this solution I used a method called GetBaseAddress for generating the server address

private static Uri GetBaseAddress()
{
    // Get the web address of the .xap that launched this application
    string strBaseWebAddress = System.Windows.Browser.HtmlPage.Document.DocumentUri.AbsoluteUri;
    // Find the position of the ClientBin directory        
    int PositionOfClientBin =
        System.Windows.Browser.HtmlPage.Document.DocumentUri.AbsoluteUri.ToLower().IndexOf(@"/client");
    if (PositionOfClientBin > -1)
    {
        // Strip off everything after the ClientBin directory         
        strBaseWebAddress = Microsoft.VisualBasic.Strings.Left(strBaseWebAddress, PositionOfClientBin);
    }
    else
    {
        strBaseWebAddress = strBaseWebAddress + @"\";
    }

    // Create a URI
    Uri UriWebService = new Uri(String.Format(@"{0}", strBaseWebAddress));
    // Return the base address          
    return UriWebService;
}

partial void LessonsByGridListDetail_Run(ref bool handled)
{
    // Set handled to 'true' to stop further processing.

}

HTML client will work more fluent with external calls, because it is not seal in Silverlight. We can call server at any time with AJAX, but what I did was to add global parameters in default.html and call service only once.

Inside default.html

var sid = "-1",
    grid = -1,
    userid = -1,
    role = "none",
    name = "none";

I added a screen without attached table or query and I name it Home. My intentions are to use this screen as menu page by adding some buttons. Each button will open a screen with list or details. But, first I will check for the session.

When designing the home screen add a Post Render Code …

PostRenderHome

The code behind is simple:

  • read session ID from query string params ( with function getParameterByName(name));
  • AJAX call the session check service;
  • parse the results ( with function getResponceParamByName(response,name));
myapp.Home.Group_postRender = function (element, contentItem) {
    // Write code here.
    $.ajax({
        type: 'post',
        data: {},
        url: '../SessionValidate/SessionCheck.ashx?sid=' + getParameterByName('sid'),
        success: function success(result) {
            grid = getResponceParamByName(result, 'grid');
            userid = getResponceParamByName(result, 'userid');
            role = getResponceParamByName(result, 'role');
            name = getResponceParamByName(result, 'name');
            sid = getResponceParamByName(result, 'sid');
        }
    });
};

function getParameterByName(name) {
    name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
    var regexS = "[\\?&]" + name + "=([^&#]*)";
    var regex = new RegExp(regexS);
    var results = regex.exec(window.location.search);
    if (results == null)
        return "";
    else
        return decodeURIComponent(results[1].replace(/\+/g, " "));
}

function getResponceParamByName(response,name) {
    name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
    var regexS = "[;]" + name + "=([^;]*)";
    var regex = new RegExp(regexS);
    var results = regex.exec(";" + response);
    if (results == null)
        return "";
    else
        return decodeURIComponent(results[1].replace(/\+/g, " "));
}

 

All this was about how to assign external authentication. The hard pars is up a head.

The LightSwitch shall work only with queries where parameters collected from external authentication are counted in.

Each time when creating a query assign the global parameters as required and then by clicking on PreprocessQuery in Write Code implement the query code.

 

newQuerye1

The code in the query shall look like this:

partial void LessonsByGrid_PreprocessQuery(decimal? GRID, ref IQueryable query)
{          
            query = from lesson in query
                    from groupLesson in lesson.GROUP_LESSONs
                    where groupLesson.GROUP_OWNERSHIP.GRID == GRID
                    select lesson;
}

From this example is visible that the filtering parameter is not used against the queried table, but to a linked table. So, this is flexible enough to work with almost any existing project.

Adding this query to a screen will create a filtering parameter on top (above the list). Hide or remove the field visualization and change the InitializeDataWorkspace code to set the value from the global params list.

Silver light example:

partial void LessonsByGridListDetail_InitializeDataWorkspace(List saveChangesTo)
{
    // Write your code here.
    LESSONGRID = Application.grid;
}

For HTML change ‘created’ event. For example:

myapp.BrowseLessonsByGrid.created = function (screen) {
    // Write code here.
    screen.LESSONGRID = grid;
};

I will not go into details about how you can implement role validation because it’s same as it’s designed by LightSwitch, except I’m using my global parameters instead of ‘Permissions’ object. And because it only work on client side (for now)

 

 


Posted

in

,

by

Tags:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.