Add a POST method to the WCF service
NOTE: This continues the series from the post Read the GET request parameters in the AfterReceiveRequest() method
I changed the name of the SchemeTest() method to SimpleTest() because the scheme test will be done on the POST request sending XML, not on a GET request.
Here is the new interface:
namespace WCFServiceWithScheme
{
[ServiceContract]
public interface ITestService
{
[OperationContract]
[WebGet(UriTemplate = "SimpleTest?name={name}")]
string SimpleTest(string name);
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "SchemeTest",
BodyStyle = WebMessageBodyStyle.Bare,
RequestFormat = WebMessageFormat.Xml)]
string SchemeTest(RequestObject theRequest);
}
}
Here are the new public methods:
namespace WCFServiceWithScheme
{
public class TestService : ITestService
{
public string SimpleTest(string name)
{
return "The name is " + name;
}
public string SchemeTest(RequestObject theRequest)
{
return "nothing interesting";
}
}
}
We have to create the simple object that will be POSTed in the request. I called it 'RequestObject'.
Notice we have included a namespace in the DataContract. This will also have to be sent in the request to the service.
namespace WCFServiceWithScheme
{
[DataContract(Namespace = "http://WCFServiceWithScheme")]
public class RequestObject
{
[DataMember]
public string FirstName { get; set; }
[DataMember]
public string LastName { get; set; }
[DataMember]
public short Age
{
get { return age; }
set { age = value; }
}
private short age = 0;
}
}
The XML submitted for this object is here. Notice the namespace is included.
<?xml version="1.0" encoding="utf-8" ?>
<RequestObject xmlns="http://WCFServiceWithScheme">
<Age>21</Age>
<FirstName>Howard</FirstName>
<LastName>Lumpy</LastName>
</RequestObject>
Read GET request parameters in the AfterReceiveRequest() method
NOTE: This continues the series from the post Expand the simple service with IDispatchMessageInspector
On a GET request, the relevant data is in the parameters. Here is how to read this data:
- Determine the request's mothod (GET or POST)
You don't know what method is called or how it was called. - Get the URITemplateMatchResults
- Get the operation name (method) that was called.
- Get the RequestUri
- Loop thru the Querry Parameters
- Loop thru the Bound Variables
NOTE: Any request to the service will run thru there. If you have 10 different GET and POST methods in your service, a request to any of them will go thru here before it goes to the called method.
public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
{
// Get the request message from the request
HttpRequestMessageProperty requestMessage = request.Properties["httpRequest"] as HttpRequestMessageProperty;
string method = string.Empty;
if (requestMessage != null)
{
// The method will be GET or POST.
method = requestMessage.Method;
}
// Get the UriTemplateMatchResults from the request.
// Here you can see some of the fun things that are revealed.
UriTemplateMatch results = internalCopy.Properties["UriTemplateMatchResults"] as UriTemplateMatch;
if (results != null)
{
// Get the operation name from the request.
// Here you can see what method was called in the Service.
// Since we have only one method (SchemeTest), it should be this.
// We can add a simple 'HelloWorld' method to see this value change.
string operationName = string.Empty;
if (results.RelativePathSegments.Count > 0)
operationName = results.RelativePathSegments[results.RelativePathSegments.Count - 1];
//
string requestUri = results.RequestUri.ToString();
// If the request method is GET, then you can play with the query parameters, uri template, and bound variables
if (method == "GET")
{
// Loop thru the querry parameters
NameValueCollection queryParameters = results.QueryParameters;
foreach (var p in queryParameters)
{
Console.WriteLine(p);
}
// The Uri template is the same as in the Attribute of the interface: [WebGet(UriTemplate = "SchemeTest?name={name}")]
string template = results.Template.ToString();
// You can loop thru the key in the variables passed in.
NameValueCollection boundVariables = results.BoundVariables;
foreach (string key in boundVariables.AllKeys)
{
// Variable names are always in UPPER case. Don't know why.
if (key == "NAME")
Console.WriteLine("This is the 'name' key");
}
}
Object correlationState = new object();
return correlationState;
}
CreateBufferedCopy of request in MessageInspector of WCF Service
NOTE: This continues the series from Expand the simple service with IDispatchMessageInspector
When your web service is called, before the request reaches the main service method, the 'AfterReceiveRequest()' method is fired.
Here is where you can inspect the request.
Very important to know, you must make a copy of the message because reading the original message changes its State from 'created' to 'copied' and downstream processing won't work. Do this by creating a buffered copy.
NOTE: This applies mostly to getting the message body from a POST request.
public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
{
Console.WriteLine("Inside the ApplyClientBehavior");
// Create a buffered copy of the request.
// You can then use the internal copy to work with.
MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
request = buffer.CreateMessage();
Message internalCopy = buffer.CreateMessage();
buffer.Close();
// Check the message state easily
Console.WriteLine("State of message: " + internalCopy.State);
Object correlationState = new object();
return correlationState;
}
WCF Service with IDispatchMessageInspector
If you want to do any pre or post inspection of a web service such as scheme validation, you can use the IDispatchMessageInspector.
This example is based on the Simple Service example. I modified the Simple Service example and added the IDispatchMessageInspector.
The IDispatchMessageInspector gives you access to 2 events.
- AfterReceiveRequest() fires before you enter the web service method:
object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext) { return new Object; }
- BeforeSendReply() fires after you leave the web service method:
void BeforeSendReply(ref Message reply, object correlationState) { }
The object returned from the first method is the 'correlationSate' object of the 2nd method. If your custom validation fails in the first, you can return a 'correlationState' object that the 2nd method will receive automatically. This allows you to return 'Bad Request' or something like that.
There is nothing unusual about the Interface for the service as you can see in ITestService.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.ServiceModel.Web;
namespace WCFServiceWithScheme
{
// NOTE: If you change the interface name "ITestService" here, you must also update the reference to "ITestService" in Web.config.
[ServiceContract]
public interface ITestService
{
[OperationContract]
[WebGet(UriTemplate = "SchemeTest?name={name}")]
string SchemeTest(string name);
}
}
There is also nothing unusual about the class that inherits from the interface. TestService.svc.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace WCFServiceWithScheme
{
public class TestService : ITestService
{
public string SchemeTest(string name)
{
return "The name is " + name;
}
}
}
The differences begin to show in the Web.config.
You will see the 'behaviorExtensions' tag and the additional addition to the 'endpointBehaviors'.
The name for the 'behaviorExtensions' is the new behavior in the 'endpointBehavior'.
The behaviorExtensions' TYPE is the name of the BehaviorExtensionElement class followed by the namespace.
The system.serviceModel in the Web.confi:
<system.serviceModel>
<!-- Required for IDispatchMessageInspector -->
<extensions>
<behaviorExtensions>
<add name="restHttpBehavior" type="WCFServiceWithScheme.MyBehaviorExtensionElement, WCFServiceWithScheme, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</behaviorExtensions>
</extensions>
<bindings>
<!-- Minimum for WebHttp -->
<webHttpBinding>
<binding name="WCFServiceWithScheme.MyDefaultBinding"/>
</webHttpBinding>
</bindings>
<behaviors>
<!-- serviceBehaviors -->
<serviceBehaviors>
<behavior name="WCFServiceWithScheme.MyServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
<!-- endpointBehaviors -->
<endpointBehaviors>
<behavior name="WCFServiceWithScheme.MyWebBehavior">
<webHttp/>
<restHttpBehavior/>
</behavior>
</endpointBehaviors>
</behaviors>
<services>
<!-- Minimum for WebHttp -->
<service behaviorConfiguration="WCFServiceWithScheme.MyServiceBehavior" name="WCFServiceWithScheme.TestService">
<!-- Minimum for WebHttp -->
<endpoint address="web" behaviorConfiguration="WCFServiceWithScheme.MyWebBehavior" binding="webHttpBinding" contract="WCFServiceWithScheme.ITestService" bindingConfiguration="WCFServiceWithScheme.MyDefaultBinding">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
</service>
</services>
</system.serviceModel>
There are 2 new classes that you must create:
* A MessageInspector class that inherits from IDispatchMessageInspector
* A BehaviorExtensionElement class that inherits from BehaviorExtensionElement and IEndpointBehavior.
The MessageInspector class contains the AfterReceiveRequest and BeforeSendReply methods.
Place break points in these methods and you can see the order in which they are fired.
The MyMessageInspector.cs class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ServiceModel.Dispatcher;
namespace WCFServiceWithScheme
{
public class MyMessageInspector : IDispatchMessageInspector
{
#region IDispatchMessageInspector Members
public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
{
Console.WriteLine("Inside the ApplyClientBehavior");
// Good idea to return error class stuff to let you know if anything happened in here
Object correlationState = new object();
return correlationState;
}
public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
// !!!!!!!!! correlationState comes from the AfterReceiveRequest method !!!!!!!!!!!!!
// If the AfterReceiveRequest method determines it is a bad request, this is where you return, 'Bad Request'
Console.WriteLine("Inside the ApplyClientBehavior");
}
#endregion
}
}
The BehaviorExtensionElement class contains several methods. The most important one is 'ApplyDispatchBehavior(). This is where you add your message inspector.
The Validate() is fired only during initialization of the service and before ApplyDispatchBehavior().
To test this when running locally, do an IISreset, place a break point in the Validate() method and the other methods, and hit F5 to start debugging.
MyBehaviorExtensionElement.cs
using System;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
namespace WCFServiceWithScheme
{
public class MyBehaviorExtensionElement : BehaviorExtensionElement, IEndpointBehavior
{
#region BehaviorExtensionElement
public override Type BehaviorType
{
get { return typeof(MyBehaviorExtensionElement); }
}
protected override object CreateBehavior()
{
return new MyBehaviorExtensionElement();
}
#endregion
#region IEndpointBehavior Members
public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
Console.WriteLine("Inside the AddBindingParameters");
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
{
Console.WriteLine("Inside the ApplyClientBehavior");
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
{
Console.WriteLine("Adding custom message inspector...");
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new MyMessageInspector());
}
public void Validate(ServiceEndpoint endpoint)
{
Console.WriteLine("Inside the Validate");
}
#endregion
}
}
WCF Web Service with Scheme Validation and other tricks
This series of posts will cover the various aspects of creating a WCF web service that will use a scheme to validate.
NOTE: This is different than the Secure WCF REST webservice I posted earlier. The previous one uses the WCF REST Starter Kit and has a very simple Web.config.
This series covers:
- Creating a Simple Service based on REST
- Expand the simple service with IDispatchMessageInspector
- Read the GET request parameters in the AfterReceiveRequest() method
- Add a POST method to the service
- How and why you should use 'CreateBufferedCopy' to clone the request
- Read the message body in a POST request
- Loading and using the scheme
- Dynamically expanding the UriTemplate on a Get request
- How to optionally embed the XSD file in the assembly
Read GET variables with JavaScript
I found 2 simple solutions to reading GET variables with JavaScript.
They use either 'document.location' or 'window.location.href' to ready the URL and split it.
The first example I found here:
function getURLVar(urlVarName) {
//divide the URL in half at the '?'
var urlHalves = String(document.location).split('?');
var urlVarValue = '';
if(urlHalves[1])
{
//load all the name/value pairs into an array
var urlVars = urlHalves[1].split('&');
//loop over the list, and find the specified url variable
for(i=0; i<=(urlVars.length); i++)
{
if(urlVars[i])
{
//load the name/value pair into an array
var urlVarPair = urlVars[i].split('=');
if (urlVarPair[0] && urlVarPair[0] == urlVarName)
{
//I found a variable that matches, load it's value into the return variable
urlVarValue = urlVarPair[1];
}
}
}
}
return urlVarValue;
}
document.write('name = ' + getURLVar('name'));
The 2nd I found here. This one is a bit more simple.
function getUrlVars()
{
var vars = [], hash;
var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
for(var i = 0; i < hashes.length; i++)
{
hash = hashes[i].split('=');
vars.push(hash[0]);
vars[hash[0]] = hash[1];
}
return vars;
}
var getvars = getUrlVars();
document.write('name = ' + getvars['name']);
I don't really like this solution because you get 'undefined' if you select a variable that does not exist. But that may be what you want to see if a variable is present in the URL.
I simplified the first example using the slice/split idea from the 2nd example and came up with a better solution:
function getURLVar(urlVarName) {
var urlVars = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
for(i=0; i<=(urlVars.length); i++)
{
if(urlVars[i])
{
var urlVarPair = urlVars[i].split('=');
if (urlVarPair[0] && urlVarPair[0] == urlVarName)
{
return urlVarPair[1];
}
}
}
return ""; // Remove this if you want it to return 'undefined' when a variable does not exist in the URL.
}
document.write('name = ' + getURLVar('name'));
POST using ASP
A more simple way to to a POST. This is cleaner than the last example I created.
I used a nice function at http://www.secretgeek.net/XMLSendReceive.shtml.
' First load the XML to send off
Set SendDoc = server.createobject("Microsoft.XMLDOM")
SendDoc.ValidateOnParse= True
SendDoc.LoadXML(sTrData)
' Then call the POST function and get a nice object back
set SourceObj = xmlSend (sURL, SendDoc)
Reading the XML:
set oUid = SourceObj.documentElement.selectSingleNode("//UserId")
set oHtml = SourceObj.documentElement.selectSingleNode("//User/Name")
Here is the nifty little function
private function xmlsend(url, docSubmit)
Set poster = Server.CreateObject("MSXML2.ServerXMLHTTP")
poster.open "POST", url, false
poster.setRequestHeader "CONTENT_TYPE", "text/xml"
poster.send docSubmit
Set NewDoc = server.createobject("Microsoft.XMLDOM")
newDoc.ValidateOnParse= True
newDoc.LoadXML(poster.responseTEXT)
Set XMLSend = NewDoc
Set poster = Nothing
end function
Simpel context menues in WPF
The first thin you must do is to create the context menu in the xaml page.
This is done as follows:
<UserControl.ContextMenu>
<ContextMenu Name="rightClickContextMenu" Visibility="Collapsed">
<MenuItem Header="Delete" Name="btnDelete" Click="btnDelete_Click"/>
<MenuItem Header="Open" Name="btnOpen" Click="btnOpen_Click"/>
<MenuItem Header="Copy" Name="btnCopy" Click="btnCopy_Click"/>
</ContextMenu>
</UserControl.ContextMenu>
Listen for the right click event and display the menu
private void m(object sender, MouseEventArgs e)
{
if (e.RightButton == MouseButtonState.Pressed)
{
rightClickContextMenu.PlacementRectangle = new Rect(e.GetPosition(diagram), new Size());
rightClickContextMenu.Visibility = Visibility.Visible;
}
}
Facebook: The very basics required for your first application
I was required to create a Facebook (Viesbook in Dutch) application for my work. I discovered how much I hate Facebook, it view of privacy, and especially, their API documentation. Their documentation is the worse I have ever seen.
If you want to work in C#, don't go to Facebook. I found a great set of Libraries from Vatlab (Vatlab - Facebook .NET Applications). The support for their libraries is excellent! I got replied back immediately.
I will now give a very basic framework (based on the Vatlap Starter Kit) that will give you an Application page and a Profile Wall-Tab widget.
Based on the Starter Kit from the VatLap package, you will have:
- Master.Master
- Default.aspx
- SecondPage.aspx
- GlobalData.cs
- Web.config - after creating your
- Images directory
- FVK directory - can be used to customize the existing functinality used in the starter kit. Not necessary to change anything to get the basic structure working.
First step:
- Sign in to your Facebook account
- Open a new tab and go to http://www.facebook.com/developer.
- Click the allow
- One the developer page, click the 'Set Up New Application' button. This process is explained in the VatLab documentation. When you are finished, you will have an API Key, Application Secret code, and an Applicatin ID. These must be copied to to your Web.config file.
Now you are ready to modify the Start Kit pages
Update Master.Master
I disabled the bookmark stuff. I am trying to keep it as simple as possible.
I also disabled the 'SecondPage.aspx'. I don't need it for this.
The Invite Friends tab I left because it works and you can ignore it. It does work well.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace MyNamespace
{
public partial class Master : System.Web.UI.MasterPage
{
protected void Page_Load(object sender, EventArgs e)
{
//AppUser loggedUser = SessionData.LoggedUser;
//if (loggedUser.bookmark.HasValue && loggedUser.bookmark == true)
//{
// bookmark1.Visible = false;
//}
if (!Page.IsPostBack)
{
tab1.AddTab("Home Page", "Default.aspx", 100);
//tab1.AddTab("Second Page", "SecondPage.aspx", 100);
tab1.AddTab("Invite Friends", "Invite.aspx", 100);
tab1.NumOfRightTabs = 1;
tab1.Distance = 5;
}
}
//protected void BookmarkCalled(object sender, EventArgs e)
//{
// //DAL.AppUserDAL.SetBookmark(SessionData.LoggedUser.id);
// bookmark1.Visible = false;
//}
}
}
On the Source page, I commented out the bookmark reference and the banner:
<%--<img src="Images/banner.png" alt="" />--%> <%--<fb:bookmark ID="bookmark1" runat="server" AppName="Facebook Start" ImageUrl="~/Images/bookmark.png" OnBookmarkCalled="BookmarkCalled" />--%>
Default page
The 'Add to profile' button is very important. This is how users can add your application to their Profile Wall-Tab without having to go to 'Account|Applications'.
You must register the user control and then add it to the page.
On the same page, you should add whatever content you want on your Applicaiton's default page.
<%@ Page Title="" Language="C#" MasterPageFile="~/Master.Master" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="MyNamespace.Default" %>
<%@ Register TagPrefix="fvk" TagName="addtoprofile" Src="~/FVK/AddtoProfileButton.ascx" %>
<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
<fvk:addtoprofile ID="addtoprofile1" runat="server" />
Hello world!
</asp:Content>
The code behind page contains the magic.
To add the Profile page Wall-Tab widget, you have to make a call to SetFBML and pass in the User ID and the HTML code you want to display in the column.
To pass in the User ID, the user must be logged in.
- RequireLogin = true;' - must be in the constructor.
- 'Facebook.Schema.user user = Api.Users.GetInfo();' - this gets the user info which includes ID, Name and all that stuff.
- 'Api.Profile.SetFBML((long)user.uid, fmbl, fmbl, null);' - The call that will build the Wall-Tab widget.
NOTE: Once built, the wall tab will not change. Another call to SetFBML must be made to change the content.
As an example, I added a very simple form. This is an AJAX type FaceBook form that will call another page and update itself. More on this below.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Facebook;
using Facebook.Web;
using FVK;
namespace MyNamespace
{
public partial class Default : CanvasIFrameBasePage
{
public Default()
{
// User must be logged in
RequireLogin = true;
}
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
GlobalData.Init();
}
// Get the user info
Facebook.Schema.user user = Api.Users.GetInfo();
if (user != null && user.uid != null)
{
// Create the HTML code to show in the Wall-Tab widget
string fmbl = "<form style=\"margin: 0px; padding: 0px;\">";
fmbl += "<div id=\"responseDiv\"></div>";
fmbl += "<input type=\"text\" name=\"requestField\" id=\"requestField\"/>";
fmbl += "<input type=\"submit\" clickrewriteurl=\"http://mydomain.com/mydir/doSomething.aspx\" clickrewriteid=\"responseDiv\" clicktoshow=\"responseDiv\" value=\"Do something\" />";
Api.Profile.SetFBML((long)user.uid, fmbl, fmbl, null);
}
else
{
// cannot get user info. Do something.
}
}
}
}
That is it!
It is really that simple. If you try to find this on your own, you will most likely run across libraries that don't work well or are not documented well. Between libraries, things work differently. So the documentation you read in one post may not apply to your work.
If the content in the Wall-Tab widget is static, how can it be updated?
Facebook created Mock-AJAX. Here is how it works:
// This DIV will be updated dynamically with the content returned. fmbl += "<div id=\"responseDiv\"></div>"; // This is a simple form tag that send content to the page behind. fmbl += "<input type=\"text\" name=\"requestField\" id=\"requestField\"/>"; // This submit tag, when clicked, will call the page 'doSomething.aspx'. The response will be placed in 'responseDiv'. fmbl += "<input type=\"submit\" clickrewriteurl=\"http://mydomain.com/mydir/doSomething.aspx\" clickrewriteid=\"responseDiv\" clicktoshow=\"responseDiv\" value=\"Do something\" />";
It works well and it is simple.
doSomething.aspx can return anything from text to HTML code.
I hope this helps someone.
Trouble with VS 2008?
If you are having trouble opening a project in VS?
It says the file have been download from the Team Foundation server, yet they are not. You may want to re-initialize Visual Studio.
Close VS.
Open the VS Command Prompt.
Enter the command: devenv /setup
With luck this will solve some of your problem. What exactly does it do? Oh the mysteries of Microsoft.
Visual Studio is not closing?
If VS refuses to close, then open the Task Manager.
In the Processes tab, kill all instinces of devenv.exe
Workspace cleaning can help speed things up.. if you are lucky.
Open the Pending Changes window (View | Other Windows | Pending Changes).
In the Workspace select box select 'Workspaces...
If you see more than one workspace you may be want to remove the ones you don't use.
Edit each one to view the Working Folders. It will show the Source Control folder and the Local folder.
If one of your workspaces has old info, remove it.
Cleaning up garbage always helps.