Wednesday, June 30, 2004

Web Service Security

Using the WS-Security you can make your web service communications more secure by using encryption, signing in addition to authentication and authorization. SOAP Header items can be encrypted using Username (user, passoword), Keberos or X.509 certificates. Electronic signatures are created using X.509 or Username tokens.

First, a few definitions:

















UsageUsernameX.509Kerberos
SigningYYN
EncryptionYYN
AuthenticationYNY
AuthorizationYNY

Thursday, June 24, 2004

Control Events

Controls can have events that are associated with them. Suppose that we had a server control that contained a single button. The button click event could perform some processing directly, or the event could call a function in another client via an exposed public event. If we have a number of different pages that contain the server control on them, each page could hook a function to the event defined in the button. When the button click event is fired, the hook function on the page is called. The page can now determine what happens in response to a click event that occurred on the button within an embedded server control. It is the page's responsibility to wire the button click event to the hook function. Note that the server control (or the button within the control) has no knowledge of what client is consuming it. Each page within the application can determine the behavior of the control event via the exposed hook function on the page. Suppose we had the following in the ascx file for the control:












In the code behind for the control we would define two EventHandlers, one for each button:

public event EventHandler Proceed;
public event EventHandler Cancel;

The button events would then forward to the hook method associated with the Proceed, or Cancel event objects.

private void btnProceed_Click(object sender, System.EventArgs e)
{
Proceed(this, e);
}

private void btnCancel_Click(object sender, System.EventArgs e)
{
Cancel(this, e);
}

The page that contains the control, sets the OnProceed and OnCancel events on the control to the hook functions in the code-behind of the page:

<%@ Register TagPrefix="ctrl" TagName="Buttons" Src="Buttons.ascx" %>

...



The code behind of the page contains the hook functions Page_Proceed and Page_Cancel. Note that the hook functions are protected. The generated class that derives from the code behind will need to access the two hook functions, so they are declared with protected visibility.

protected void Page_Proceed(object sender, System.EventArgs e)
{
// do something for proceed
}

protected void Page_Cancel(object sender, System.EventArgs e)
{
// do something for cancel
}

When the aspx page is parsed and the dynamic class is generated, the control properties OnProceed and OnCancel are translated into the following code (in the generated page class):

NavigateButtons.Proceed += new EventHandler(this.Page_Proceed);
NavigateButtons.Cancel += new EventHandler(this.Page_Cancel);

When the Button1_Click event in the control fires, the Page_Proceed method is called in the page. Each page can now define hook methods that should be called when the button click events on the contained control are fired. Each page defines the hook functions, and the control tag in the .aspx page is modified to associate the hook methods with events in the control:

* Add the EventHandler declarations in the control
* Add the hook functions in the parent page that should be called when the event in the control is triggered
* Add the server control tag to the .aspx file associated with the page
* Add the On... attributes to the server control tag and set the attributes to the appropriate hook function in the page. Make sure that the attribute is named the same as the declared event in the control, prefixed by On. So if the event field in the control is Proceed, the attribute on the control in the .aspx page should be in the format OnProceed="hook function name in page"

Now each page can define their own hook functions for the Proceed and Cancel events exposed by the control:

// page A


// page B


The functions within each page (PageA_Proceed, PageB_Proceed) do not have to be named uniquely. Both pages could contain a Page_Proceed and Page_Cancel hook functions, the only difference being that the Page_Proceed and Page_Cancel in each page would perform different operations.

Control Validation (brief)

Let's say that we have the following simple page with one embedded server control (button) on the aspx page the following is the order of events that happen when the page is initially displayed:

1. The child controls' OnInit event is fired
2. The page OnInit event is fired
3. The Page's TrackViewState() is executed
4. The Page's OnLoad (Page_Load) event is fired
5. The child controls' OnLoad (Page_Load) event is fired
6. The Page's OnPreRender event is fired
7. The Page's SaveViewState() is run
8. The Page's SaveViewState() is run

If we have a click event defined for the button, called btn1_click, and inside this click event it executes a callback function called Page_Proceed on the page via postback, the order is:

1. The child controls' OnInit event is fired
2. The page OnInit event is fired
3. The Page's TrackViewState() is executed
4. The Page's OnLoad (Page_Load) event is fired
5. The child controls' OnLoad (Page_Load) event is fired
6. The Page's RaisePostBackEvent is fired
7. The child controls' btn1_Click event is fired
8. The Page's Page_Proceed() method is executed
9. The Page's OnPreRender event is fired
10. The Page's SaveViewState() is run
11. The Page's SaveViewState() is run

A few new events are added to the sequence: RaisePostBackEvent, btn1_Click, and the Page_Proceed event. The RaisePostBackEvent is used to trigger a server side event. The RaisePostBackEvent is defined as:

protected override void RaisePostBackEvent(IPostBackEventHandler sourceControl, string eventArgument)

RaisePostBackEvent takes two arguments: the IPostBackEventHandler derived control that is raising the event and a string argument. The RaisePostBackEvent will determine which function in the codebehind of the control to execute. If a textbox and a RangeValidator control are placed on the page, the following order is observerd (if the validation was successful):

1. The child controls' OnInit event is fired
2. The page OnInit event is fired
3. The Page's TrackViewState() is executed
4. The Page's OnLoad (Page_Load) event is fired
5. The child controls' OnLoad (Page_Load) event is fired
6. The Page's RaisePostBackEvent is fired
7. The Page's Validate() is fired
8. The child controls' btn1_Click event is fired
9. The Page's Page_Proceed() method is executed
10. The Page's RaisePostBackEvent finishes
11. The Page's OnPreRender event is fired
12. The Page's SaveViewState() is run
13. The Page's SaveViewState() is run

If the validation was not successful, no postback is processed, as all of the validation occurs on the client. One thing to note is that the fired event plus the Validate() methods are executed within the context of the RaisePostBackEvent. Usually a page processes only one event, so the RaisePostBackEvent waits until the fired event and its Validate() function to run before exiting. If the Validate() method of the Page fails, the fired event does not run. If we take a view of the source for the page in the browser, we see:





This basically calls the javascript function Page_ClientValidate(), which is located in /aspnet_client/system_web/1_1_4322/WebUIValidation.js:

function Page_ClientValidate()
{
var i;
for (i = 0; i < Page_Validators.length; i++) {
ValidatorValidate(Page_Validators[i]);
}
ValidatorUpdateIsValid();
ValidationSummaryOnSubmit();
Page_BlockSubmit = !Page_IsValid;
return Page_IsValid;
}

This function iterates over each validator and calls the ValidatorValidate() function for each validator. Depending on the type of validator (RequiredField, RequiredRange, RegularExpression, etc.) a corresponding function exists in WebUIValidation.js:

* CompareValidatorEvaluateIsValid
* CustomValidatorEvaluateIsValid
* RegularExpressionValidatorEvaluateIsValid
* RequiredFieldValidatorEvaluateIsValid
* RangeValidatorEvaluateIsValid

The ValidatorValidate() uses the evaluationfunction property of each validator to execute one of the functions above:

function ValidatorValidate(val)
{
val.isvalid = true;
if (val.enabled != false) {
if (typeof(val.evaluationfunction) == "function") {
val.isvalid = val.evaluationfunction(val);
}
}
ValidatorUpdateDisplay(val);
}

Once evaluationfunction has run the isvalid field on each validator has been set. The ValidatorUpdateDisplay() function then runs. This function will then display the item within the span tag that is associated with the validator:

!

The ValidatorUpdateDisplay() will toggle whether the item within the 'span' tag is set to the 'hidden' or 'visible' state.

function ValidatorUpdateDisplay(val)
{
if (typeof(val.display) == "string") {
if (val.display == "None") {
return;
}
if (val.display == "Dynamic") {
val.style.display = val.isvalid ? "none" : "inline";
return;
}
}
val.style.visibility = val.isvalid ? "hidden" : "visible";
}

If the validation succeeds, the item within the span tag remains hidden, otherwise it is visible.

Creating an XML driven web site

An XML driven website is a website that stores the content as XML, either in a file or in a database. When a user requests a page, the XML content is retrieved from the data store and rendered as HTML in the browser. The HTML is usually created by a transformation, such as XSLT, or the HTML can be created programmatically. In either case, the XML data is merged with the markup to create the returned HTML page.

public void GetContent(string loc)
{
XmlTextReader reader = null;

try
{
reader = new XmlTextReader(Server.MapPath(loc));

if (reader != null)
RenderContent(reader);
}
catch(Exception e)
{
string err = e.ToString();
}
}


The nodes in an XML stream can be accessed by an XMLDocument, an XMLTextReader/XMLTextWriter and an XPathNavigator. Each method has its advantages and drawbacks:

* XmlDocument: Provides direct memory access to the entire xml document. Nodes can be selected from the XmlDocument by XPath expressions. Can use a large amount of memory resources.
* XMLTextReader: Provide forward-only access to an xml stream. The entire xml dataset is not loaded into memory all at once, the XmlTextReader reads in nodes on an as-needed basis. The XMLTextWriter writes xml data to a file in a forward-only manner.
* XPathNavigator: Provides bi-directional access to nodes in a XmlDocument or XPathDocument. The XPathDocument provides a fast read-only cache for an xml document. If you are using an XmlTextReader to access the xml, an XPathDocument can be created by passing in a XmlTextReader object into the XPathDocument constructor. This object is not created directly, it is returned from a call to the CreateNavigator() method on the XmlDocument/XPathDocument class.



When inserting text into an XML file, beware of the special characters <, >, ', ", &. They should be escaped by using the special notation.


When passing XML data in memory you can use the following object types:

* XmlNode. Provides an interface to an in-memory tree representation of an XML document. Gives a R/W interface to the XML data structure in addition to XPath expression selections. The XmlDocument inherits the XmlNode abstract class.
* XmlReader. Streaming forward only object. Provides an xml facade over an existing object graph/text/binary data object. Difficult to use.
* XPathNavigator. Traverses xml in a bi-directional manner. Able to navigate an xml document via XPath expressions. This is the preferred way to pass xml around in the CLR.

Building Web Services Today (like, right now!)

Rich Turner from the Indigo team has a post about creating web services using existing technologies:
Building Service Today.

In general, he has a few points:


Tuesday, June 22, 2004

Web Services exposing internal object types

Lately I have been looking at creating some WSDL for a few web services for the product that I am working on.
One of the issues that I am having is how to maximize the flexibility of the system by making sure that each layer within the web service hierarchy (web service stub/business/data layer) is independent of one another. One of the decisions that I have to make is whether or not to return a domain model object type via a web service method. For instance, suppose that I have the following domain model object:

Case A:

public class Product
{
public string name;
public string sku;
public double price;

public Product() {}

public Product(string name, string sku, double price)
{
this.name = name;
this.sku = sku;
this.price = price;
}
}


My business layer has a method that retrieves an object of type Product:

public class ProductBusiness
{
public Product retrieveProduct()
{
return new Product("JellyBelly (8 oz.)", "PK09834", 2.50);
}
}

My web service calls the retrieveProduct method:

[WebMethod]
public Product getProduct()
{
ProductBusiness bus = new ProductBusiness();
Product prod = bus.retrieveProduct();
return prod;
}


In this example, the WSDL is driven by the domain model (what I call schema last). There
are a few drawbacks to this approach:


One solution would be to add a mapper class that would translate between the classes exposed via the public web service (WSDL) and the classes used internally by the business layer. One way to do this is to define the WSDL independently of any application domain model classes.

Case B:
Suppose that I defined the WSDL for a product called WSProduct:

public class WSProduct
{
public string name;
public string sku;
public double price;
public DateTime dateRetrieved;
}

And change the web method to use this class:

[WebMethod]
public WSProduct getWSProduct()
{
ProductBusiness bus = new ProductBusiness();
WSProduct prod = bus.retrieveWSProduct();
return prod;
}

Now the web service method returns a class of type WSProduct, and it obtains
the object from the ProductBusiness class via the retrieveWSProduct() method.
The retrieveWSProduct method on the ProductBusiness class looks like this:

public WSProduct retrieveWSProduct()
{
Product prod = new Product("JellyBelly (8 oz.)", "PK09834", 2.50);
WSProduct wsProd = new WSProduct();
wsProd.name = prod.name;
wsProd.sku = prod.sku;
wsProd.price = prod.price;
wsProd.dateRetrieved = DateTime.Now;
return wsProd;
}

The method creates a Product and a WSProduct class, and then it translates(copies)
the values from the Product class to the WSProduct class. It also sets the dateRetrieved field on the WSProduct class.

In this case, any changes to the Product domain model class will not affect the contract
exposed by the web service, since this contract is represented by the WSProduct definition
in the WSDL. Any changes to the WSProduct in the WSDL will only affect the ProductBusiness
object (retrieveProduct will need modification). The Product object will remain unchanged since it is only used internally and it is not exposed by the web service.

Now for the question:

This page is powered by Blogger. Isn't yours?