MapMyName Project
Two students from Aveiro's University want to calculate the number of Internet users in the world, in a month. The project is called MapMyName and will serve to also geographically map the users.
Do you want to see where I live?
BUG: Using Custom Identities in ASP.NET fails when using the ASP.NET Developement Server
This bug has been around for a while and affects web site development when using custom identities (classes that implement System.Security.Principal.IIdentity) and the ASP.NET Development Server.
To reproduce this bug, you have to implement the IIdentity interface, something like this:
[Serializable]
public class MyIdentity : System.Security.Principal.IIdentity
{
private string name;
private string authenticationType;
private bool isAuthenticated;
public MyIdentity(string name, string authenticationType, bool isAuthenticated)
{
this.name = name;
this.authenticationType = authenticationType;
this.isAuthenticated = isAuthenticated;
}
#region IIdentity Members
public string AuthenticationType
{
get { return this.authenticationType; }
}
public bool IsAuthenticated
{
get { return this.isAuthenticated; }
}
public string Name
{
get { return this.name; }
}
#endregion
}
And use it somewhere like this:
protected void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
Thread.CurrentPrincipal = HttpContext.Current.User =
new GenericPrincipal(new MyIdentity(HttpContext.Current.User.Identity.Name, "test", true), new string[0]);
}
It doesn't happen all the time but it happens. Eventually, you'll get this in your event log:
Event Type: Warning
Event Source: ASP.NET 2.0.50727.0
Event Category: Web Event
Event ID: 1309
Date: 29-04-2007
Time: 18:34:54
User: N/A
Computer: ORCASBETA1VSTS
Description:
Event code: 3005
Event message: An unhandled exception has occurred.
Event time: 29-04-2007 18:34:54
Event time (UTC): 30-04-2007 1:34:54
Event ID: 8afb62b7b7604da087509636bb48fbad
Event sequence: 16
Event occurrence: 1
Event detail code: 0
Application information:
Application domain: 53ad158c-10-128223704725800576
Trust level: Full
pplication Virtual Path: /WebSite3
Application Path: C:\Documents and Settings\Administrator\My Documents\Visual Studio Codename Orcas\WebSites\WebSite3\
Machine name: ORCASBETA1VSTS
Process information:
Process ID: 3880
Process name: WebDev.WebServer.EXE
Account name: ORCASBETA1VSTS\Administrator
Exception information:
Excpetion type: SerializationException
Exception message: Type is not resolved for member 'MyIdentity,App_Code.4broaqvc, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'.
Request information:
Request URL: http://localhost:1137/WebSite3/default.aspx
Request path: /WebSite3/default.aspx
User host address: 127.0.0.1
User: user
Is authenticated: True
Authentication Type: test
Thread account name: ORCASBETA1VSTS\Administrator
Thread information:
Thread ID: 4
Thread account name: ORCASBETA1VSTS\Administrator
Is impersonating: False
Stack trace:
at Microsoft.VisualStudio.WebHost.Connection.ReadRequestBytes(Int32 maxBytes)
at Microsoft.VisualStudio.WebHost.Request.ReadEntityBody(Byte[] buffer, Int32 size)
at System.Web.HttpRequest.GetEntireRawContent()
at System.Web.HttpRequest.FillInFormCollection()
at System.Web.HttpRequest.get_Form()
at System.Web.HttpRequest.get_HasForm()
at System.Web.UI.Page.GetCollectionBasedOnMethod(Boolean dontReturnNull)
at System.Web.UI.Page.DeterminePostBackMode()
at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
I've opened a bug on this. If you are having the same problem, vote on it.
With the bug, I've submitted this sample.
WCF: Building an HTTP User Agent Message Inspector
Yesterday I had to build a custom message encoder to be able to call a legacy POX service with iso-8859-1 encoding.
It turned out that the service had another surprise to me: it needs an HTTP user-agent header.
It's something quite simple to accomplish with WCF. All it's needed is to add the required header to the HTTP request message property of the request message. Something like this:
HttpRequestMessageProperty httpRequestMessage = new HttpRequestMessageProperty();
httpRequestMessage.Headers.Add(USER_AGENT_HTTP_HEADER, this.m_userAgent);
requestMessage.Properties.Add(HttpRequestMessageProperty.Name, httpRequestMessage);
But I was not willing to pollute my code with this and, besides that, it doesn't look much WCFish having to do this.
Since I don't like to spend too much time trying to figure things out when I know someone who knows it, I asked António Cruz what to do. And the answer was a client message inspector.
With the help of a sample I have built an HTTP user agent client message inspector.
Message Inspector
The message inspector is very simple. We just need to implement the IClientMessageInspector Interface.
public class HttpUserAgentMessageInspector : IClientMessageInspector
{
private const string USER_AGENT_HTTP_HEADER = "user-agent";
private string m_userAgent;
public HttpUserAgentMessageInspector(string userAgent)
{
this.m_userAgent = userAgent;
}
#region IClientMessageInspector Members
public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
}
public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
HttpRequestMessageProperty httpRequestMessage;
object httpRequestMessageObject;
if (request.Properties.TryGetValue(HttpRequestMessageProperty.Name, out httpRequestMessageObject))
{
httpRequestMessage = httpRequestMessageObject as HttpRequestMessageProperty;
if (string.IsNullOrEmpty(httpRequestMessage.Headers[USER_AGENT_HTTP_HEADER]))
{
httpRequestMessage.Headers[USER_AGENT_HTTP_HEADER] = this.m_userAgent;
}
}
else
{
httpRequestMessage = new HttpRequestMessageProperty();
httpRequestMessage.Headers.Add(USER_AGENT_HTTP_HEADER, this.m_userAgent);
request.Properties.Add(HttpRequestMessageProperty.Name, httpRequestMessage);
}
return null;
}
#endregion
Behavior
But this message inspectors are extensions to the client runtime. Such extensions are configured using behaviors. A behavior is a class that changes the behavior of the service model runtime by changing the default configuration or adding extensions (such as message inspectors) to it.
The implementation of a behavior is, also, very simple. We just need to implement the IEndpointBehavior Interface. In this case, since it's a client message inspector only, all we need to implement is the ApplyClientBehavior method.
public class HttpUserAgentEndpointBehavior : IEndpointBehavior
{
private string m_userAgent;
public HttpUserAgentEndpointBehavior(string userAgent)
{
this.m_userAgent = userAgent;
}
#region IEndpointBehavior Members
public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
{
HttpUserAgentMessageInspector inspector = new HttpUserAgentMessageInspector(this.m_userAgent);
clientRuntime.MessageInspectors.Add(inspector);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
{
}
public void Validate(ServiceEndpoint endpoint)
{
}
#endregion
}
Adding the Message Inspector through Configuration
For configuring an endpoint in the application configuration file to use our custom behavior, the service model requires us to create a configuration extension element represented by a class derived from BehaviorExtensionElement.
public class HttpUserAgentBehaviorExtensionElement : BehaviorExtensionElement
{
public override Type BehaviorType
{
get
{
return typeof(HttpUserAgentEndpointBehavior);
}
}
protected override object CreateBehavior()
{
return new HttpUserAgentEndpointBehavior(UserAgent);
}
[ConfigurationProperty("userAgent", IsRequired = true)]
public string UserAgent
{
get { return (string)base["userAgent"]; }
set { base["userAgent"] = value; }
}
}
This extension must then be added to the service model's configuration section for extensions:
<system.serviceModel>
...
<extensions>
<behaviorExtensions>
<add name="httpUserAgent" type="Pajocomo.ServiceModel.Dispatcher.HttpUserAgentBehaviorExtensionElement, Esi.ServiceModel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
...
</system.serviceModel>
Note: We must use the full assembly qualified name here, otherwise it won't work.
Now we need to apply our behavior extension to a behavior configuration:
<system.serviceModel>
...
<behaviors>
<endpointBehaviors>
<behavior name="LegacyServiceEndpointBehavior">
<httpUserAgent userAgent="test user agent" />
</behavior>
</endpointBehaviors>
</behaviors>
...
</system.serviceModel>
Adding the configuration to the service configuration
Now we can apply this configuration to our sevice configuration:
<system.serviceModel>
...
<client>
<endpoint
address="..."
binding="..."
bindingConfiguration="..."
behaviorConfiguration="LegacyServiceEndpointBehavior"
contract="..."
name="..." />
</client>
...
</system.serviceModel>
And we are all set to go.
WCF: Text Message Encoding and ISO-8859-1 Encoding
I'm a newbie in WFC and, so far, only have done client code to call POX web services.
I've been using a textMessageEncoding binding extension with a message version of None and a write encoding of utf-8 and all has been running fine.
Well, until I needed to call a service in iso-8859-1 encoding. Then I started getting a ProtocolException:
The content type text/xml;charset=iso-8859-1 of the response message does not match the content type of the binding (text/xml; charset=utf-8). If using a custom encoder, be sure that the IsContentTypeSupported method is implemented properly.
Bruno directed me to a sample that looked promising. The sample works fine because it's using the same binding extension in the server and in the client.
When I tried to use the customTextMessageBinding from the sample, I got this nice ProtocolException:
The content type text/xml;charset=iso-8859-1 of the response message does not match the content type of the binding (text/xml; charset=iso-8859-1). If using a custom encoder, be sure that the IsContentTypeSupported method is implemented properly.
Looks kind of funny, doesn't it?
It comes down to the fact that the base MessageEncoder class doing a string comparison on the content type.
To solve this, I've overriden the IsContentTypeSupported method and added an additional validadion when the base validation fails. This new validation only checks for the media type of the response and lets the XmlReader handle the encoding.
public override bool IsContentTypeSupported(string contentType)
{
if (base.IsContentTypeSupported(contentType))
{
return true;
}
if (contentType.Length == this.MediaType.Length)
{
return contentType.Equals(this.MediaType, StringComparison.OrdinalIgnoreCase);
}
else
{
if (contentType.StartsWith(this.MediaType, StringComparison.OrdinalIgnoreCase)
&& (contentType[this.MediaType.Length] == ';'))
{
return true;
}
}
return false;
}
Since this seems to be happening in Orcas too, I've added a comment to the sample documentation and opened an issue. Vote on them.
How to use Forms Authentication’s User Definitions with the Login Control
In ASP.NET 1.1 we already had definitions of name and password credentials within the configuration file. We just didn't have a Login control.
Now we have both but the Login control is set by default to use the default membership provider.
How can we use both of them together?
Well, there are two ways, depending on your will or needs:
- Using the Login control with FormsAuthentication
This is the easiest way, you just need to handle the Authenticate event of the Login control.
Login.aspx
<!-- ... -->
<asp:Login ID="Login1" runat="server" DisplayRememberMe="False" OnAuthenticate="Login1_Authenticate">
</asp:Login>
<!-- ... -->
// ...
protected void Login1_Authenticate(object sender, AuthenticateEventArgs e)
{
e.Authenticated = FormsAuthentication.Authenticate(Login1.UserName, Login1.Password);
}
// ...
- Using FormsAuthentication as the membership provider
This one is a bit trickier.
First you don't need to handle the Authenticate event.
<!-- ... -->
<asp:Login ID="Login1" runat="server" DisplayRememberMe="False">
</asp:Login>
<!-- ... -->
Then you need to implement a membership provider that uses FormsAuthentication.
public class FormsAuthenticationMembershipProvider : System.Web.Security.MembershipProvider
{
private string applicationName;
public override string ApplicationName
{
get { return this.applicationName; }
set { this.applicationName = value; }
}
public override bool EnablePasswordReset
{
get { return false; }
}
public override bool EnablePasswordRetrieval
{
get { return false; }
}
public override bool ValidateUser(string username, string password)
{
return FormsAuthentication.Authenticate(username, password);
}
// Not Implemented MembershipProvider Members
}
Architecture Tools
In his show about Scenario Based Architecture Validation, Ron Jacobs talks with Dragos Manolescu (Architecture Evaluation in Practice) about how difficult it is to evaluate and validate an architecture.
There are a few tools that can help you validate the architecture of your application or framework, though. Here are two of them.
Lattix LDM 3.0
This tool from Lattix uses Dependency Structure Matrix (DSM) to communicate the architecture, identify critical dependencies, and define rules to flag dependency violations.
- Where can it be used?
- Java
jar, zip or class files - C/C++
Microsoft Visual Studio C/C++: bsc files
All other C/C++: Doxygen xml output files - .NET
exe and dll files - Oracle
9i or 10g database catalog
- Java
- What I like about it
- The way I can tweak my architecture and get a work list with what I need to get there.
- I can define my own validation rules.
- Some level of integration with Visual Studio 2005 that keeps the model in sync with the builds.
- What I don't like about it
- I have nothing against Java, but I'm a .NET developer and I don't like the fact that I need to install Java to work with my .NET assemblies.
- What I would like it to have
- A .NET version.
- SQL Server support.
- More integration with Visual Studio (build targets, check in policies, etc.).
NDepend
This is a tool from fellow C# MVP Patrick Smacchia, and goes more deeply in the application's structure. Besides being an analysis tool, this is also a diagnostics tool that can be integrated in build processes to guarantee architectural quality.
- Where can it be used?
- Any .NET assembly.
- What I like about it
- Tree map of assemblies/classes
- Dependence Structure Matrix.
- CQL (Code Query Language). An amazing SQL like query language used to query the code and extract metrics or define constraints.
- Build comparison.
- Import Visual Studio 2005 solutions and projects.
- Integration with MSBuild, NAnt and CruiseControl.NET.
- What I don't like about it
- The NDpend Console that pops up.
- What I would like it to have
- The NDepend Console running integrated in VisualNDepend.
- More analysis and prescriptive tools to help us achieve a better architecture.
This is, by no means, an extensive analysis of these two very complex tools and you should try them by yourselves (or, at leat, watch the various screen casts provided) to come to your own conclusions.
What’s wrong with ASP.NET provider model? – The XmlSiteMapProvider
Besides the problem with spaces in query strings (the same thing happens for the entire URL), you cannot subclass the XmlSiteMapProvider and use it if your site has site map definitions in files placed in each folder of the site instead of the a single one in the root of the site.
Suppose you want to build a site map provider that works like the XmlSiteMapProvider but the security trimming is based solely on the user's roles and not taking into account URL or File authorization.
The way security trimming is supposed to work with the XmlSiteMapProvider doesn't allow you to do this because it will show in the site map all resources accessible to the user independently of the roles declared in the site map definition.
(Before someone starts to complain about security issues, let me remember you that I can have my own authorization module that won't allow access to resources that aren't in the site map)
Such a site map provider can easily be developed. It can be something like this:
public class MyXmlSiteMapProvider : System.Web.XmlSiteMapProvider
{
public override bool IsAccessibleToUser(System.Web.HttpContext context, System.Web.SiteMapNode node)
{
if (node == null)
{
throw new System.ArgumentNullException("node");
}
if (context == null)
{
throw new System.ArgumentNullException("context");
}
if (!this.SecurityTrimmingEnabled)
{
return true;
}
System.Collections.IList roles = node.Roles;
System.Security.Principal.IPrincipal user = context.User;
if (user == null || roles == null || roles.Count == 0)
{
return true;
}
foreach (string role in roles)
{
if ((role == "*") || user.IsInRole(role))
{
return true;
}
}
return false;
}
}
Now, suppose your site has some sub-folders (Customers, Suppliers, Admin) and you want to define the site map structure for each sub-folder in each sub-folder. Something like this:
<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
<siteMapNode url="~/Default.aspx" title="Home" description="Home Page" roles="*">
<siteMapNode siteMapFile="~/Customers/Web.sitemap" />
<siteMapNode siteMapFile="~/Suppliers/Web.sitemap" />
<siteMapNode siteMapFile="~/Admin/Web.sitemap" />
</siteMapNode>
</siteMap>
Well, if you are sub-classing the XmlSiteMapProvider, you can't. You'll have to fully develop your own XML site map provider. You don't have to type it yourself [^], but you'll have to maintain it.
It could be avoided if the XmlSiteMapProvider had been built with extensibility in mind (like the DataTable, for example) by adding a BuildNewXmlSiteMapProvider virtual method responsible for creating new XmlSiteMapProvider instances. This way, your provider could create its own new instances.
If you would like to see this implemented in ASP.NET, vote on my suggestion.
You can workaround this by adding a site map provider for each sub-folder:
<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
<siteMapNode url="~/Default.aspx" title="Home" description="Home Page" roles="*">
<siteMapNode provider="CustomersSiteMap" />
<siteMapNode provider="SuppliersSiteMap" />
<siteMapNode provider="AdminSiteMap" />
</siteMapNode>
</siteMap>
Adding Presenters to MasterPages in the Web Client Software Factory
Master pages, in the Web Client Software Factory, act as equivalents to the Shell in the Smart Client Software Factory but, unlike their smart client counterpart, they don't benefit from dependency injection.
I've just cooked up a quick way to add dependency injection to master pages in the Web Client Software Factory and, thus, add a Presenter (with the associated Controller).
This can be done just by adding a few lines of code to the Microsoft.Practices.CompositeWeb.WebClientApplication class:
protected void InnerPreRequestHandlerExecute(IHttpContext context)
{
if (HttpRequestHelper.IsHandledByPageHandlerFactory(context.Request.Url.ToString()))
{
ICompositionContainer moduleContainer = GetModuleContainer(context);
// We need to use a non-singleton based builder object here, otherwise
// every object created from the aspx page with the BuildNew attribute
// will be created with hard references on the lifetime container
// and will never be released, causing a memory leak.
CompositionContainer.BuildItem(PageBuilder, moduleContainer.Locator, context.Handler);
Page page = context.Handler as Page;
if (page != null)
{
page.PreInit += new EventHandler(OnPagePreInit);
PrePageExecute(page);
}
}
}
private void OnPagePreInit(object sender, EventArgs e)
{
Page page = sender as Page;
page.PreInit -= new EventHandler(OnPagePreInit);
if (page.Master != null)
{
ICompositionContainer moduleContainer = GetModuleContainer(new CompositeWeb.Web.HttpContext(HttpContext.Current));
CompositionContainer.BuildItem(PageBuilder, moduleContainer.Locator, page.Master);
}
}
With this little change, master pages can be built the same way pages are:
public partial class MasterPage : System.Web.UI.MasterPage, IMasterView
{
private MasterPresenter _presenter;
protected void Page_Load(object sender, EventArgs e)
{
if (!this.IsPostBack)
{
this._presenter.OnViewInitialized();
}
this._presenter.OnViewLoaded();
}
[Microsoft.Practices.ObjectBuilder.CreateNew]
public MasterPresenter Presenter
{
get { return this._presenter; }
set
{
this._presenter = value;
this._presenter.View = this;
}
}
#region IMasterView Members
// ...
#endregion
}
If you want to see this in the Web Client Software Factory, vote on the corresponding work item.
Update
Apparently I had missed a previous post from Simon Ince on something like this.
There's a discussion on this on the community site.
David Hayden also posted something.
Recent Comments