You can configure Web applications in Windows SharePoint Services 3.0 to be accessed by up to five different authentication methods or identity management systems. Let’s imagine a partner application that is configured to be accessed by users from two different identity management systems. Internal employees are authenticated by using one of the standard Windows authentication methods. External collaborators are authenticated against another identity management system.
To configure a Web application to be accessed by two or more different authentication systems, you must configure additional zones for the Web application. Zones represent different logical paths of gaining access to the same physical application. With a typical partner application, employees of a partner company access the application through the Internet, while internal employees access the application directly through the intranet.
To create a new zone, extend the Web application. On the Extend Web Application to Another IIS Web Site page, in the Load Balanced URL section, specify the URL and zone type. The zone type is simply a category name applied to the zone and does not affect the configuration of the zone.
“Forms” authentication method configuration:
- Create a database for the authentication.
- Run aspnet_regsql.exe - A all -E. This would create the aspnetdb in your default instance, using Windows Authentication. This utility is located at C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727
- Set permissions to the aspnetdb database.
-
- Configure authentication method of a Web application.
a. Go to Central Administration -> Application Management -> Application Security -> Authentication providers, click the zone to open the Edit Authentication page.
b. Change the Authentication Type from “Windows” to "Forms", type in “AspNetSqlMembershipProvider” as the Membership provider name, type in “AspNetSqlRoleProvider” as the Role manager name. Save it.
4. Register the membership and role provider in the Web.config file for the SharePoint Web application.
a. Include this lines inside <configuration> tag:
<connectionStrings>
<add name="AspNetSqlProvider" connectionString="server=SQLSERVERMACHINE; database=aspnetdb; Trusted_Connection=True" />
</connectionStrings>
<system.web>
<membership defaultProvider="AspNetSqlMembershipProvider">
<providers>
<remove name="AspNetSqlMembershipProvider" />
<add connectionStringName="AspNetSqlProvider" name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</providers>
</membership>
<roleManager enabled="true" defaultProvider="AspNetSqlRoleProvider">
<providers>
<remove name="AspNetSqlRoleProvider" />
<add connectionStringName="AspNetSqlProvider" name="AspNetSqlRoleProvider" type="System.Web.Security.SqlRoleProvider, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</providers>
</roleManager>
</system.web>
b. Substitute SQLSERVERMACHINE for the name of the Sql Server machine.
5. Register the membership provider in the Web.config file for the Central Administration site.
a. Include this lines inside <configuration> tag:
<connectionStrings>
<add name="AspNetSqlProvider" connectionString="server=SQLSERVERMACHINE; database=aspnetdb; Trusted_Connection=True" />
</connectionStrings>
<system.web>
<membership defaultProvider="AspNetSqlMembershipProvider">
<providers>
<remove name="AspNetSqlMembershipProvider" />
<add connectionStringName="AspNetSqlProvider" name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</providers>
</membership>
<roleManager enabled="true" defaultProvider=" AspNetWindowsTokenRoleProvider">
<providers>
<remove name="AspNetSqlRoleProvider" />
<add connectionStringName="AspNetSqlProvider" name="AspNetSqlRoleProvider" type="System.Web.Security.SqlRoleProvider, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</providers>
</roleManager>
</system.web>
b. Attention to the <roleManager> tag. The defaultProvider must be AspNetWindowsTokenRoleProvider.
c. Substitute SQLSERVERMACHINE for the name of the Sql Server machine.
6. Add custom users to the aspnetdb database.
a.
7. Set the Site Collections administrators
a. Go to Central Administration -> Application Management -> SharePoint Site Management -> Site collection administrators, select the right Site Collection.
b. Type in a valid custom user as the Primary site collection administrator.
References
· Plan authentication methods (Windows SharePoint Services)
o http://technet2.microsoft.com/windowsserver/WSS/en/library/b6bc8fec-c11c-4ed7-a78d-3ad61c7ef6c01033.mspx?mfr=true
· Sharepoint 2007 - Enabling Custom Authentication using a Custom Membership Provider
o http://blah.winsmarts.com/2006/05/19/sharepoint-2007--enabling-custom-authentication-using-a-custom-membership-provider.aspx
· How To: Use Forms Authentication with SQL Server in ASP.NET 2.0
o http://msdn2.microsoft.com/en-us/library/ms998317.aspx
· Customizing and Branding Web Content Management-Enabled SharePoint Sites (Part 3 of 3): Creating and Configuring WCM-Enabled Sites
o http://msdn2.microsoft.com/en-us/library/aa830817.aspx
· Microsoft SharePoint Products and Technologies Team Blog
o http://blogs.msdn.com/sharepoint/archive/2006/08/16/702010.aspx
Introduction
This week it was asked to me to find some elegant and practical way to use WCF. After some reading (very good Dong’s article http://blogs.msdn.com/wenlong/archive/2007/10/27/performance-improvement-of-wcf-client-proxy-creation-and-best-practices.aspx) I reached some issues:
1. I want to write only one client code for running both local and remote objects (like the good old Remoting).
2. Wouldn’t it be nice if I could build a WCF client application without the needing Svcutil.exe tool?
3. I don’t want to call Close() every time I’m done with the proxy. Why don’t use Dispose() and using {}?
4. I want to be able to cache proxy objects.
So, here are some ideas to help solve these issues.
Ideas
First I decided to build an object factory that I’ll use from the presentation tier to call business objects from the business tier. This object factory has a method public static T CreateInstance<T>() that is responsible to instantiate the object of type T. They can be local or remote objects, depending on some configuration settings. This way I only write one client code, and I solve the issue #1.
To solve the others issues I created a base class Proxy<T> : IDisposable, where T is an interface. It will be the base of all proxy objects. This class creates and opens an internal ChannelFactory<T> object in its constructor, and closes it in its Dispose() method.
That gives us two things: a chance to cache both the ChannelFactory and/or the proxy object; and let us to benefit from the using {} structure, so the Close() method is always called.
Now is the tricky part. In order to eliminate the need of using the Svcutil.exe tool, we need to “build” the proxy class at runtime. The result is basically a class that derives from Proxy<T> and implements T.
Some code
So let’s code!
This is the base class that all proxies derive from:
public class Proxy<T> : IDisposable
{
private bool m_disposed = false;
private ChannelFactory<T> m_factory = null;
private T m_bo = default(T);
protected Proxy()
{
//In this version it's always created a new ChannelFactory and proxy.
//Next version it could get these objects from a pool.
m_factory = new ChannelFactory<T>(typeof(T).FullName);
m_factory.Open();
m_bo = m_factory.CreateChannel();
}
// This is how the derived proxy type talks to the remote object.
protected T BO
{
get { return m_bo; }
}
#region IDisposable Members
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!this.m_disposed)
{
if (disposing)
{
// Next version, instead of releasing the objects, it'll return them to the pool.
// Dispose managed resources.
m_bo = default(T);
if (m_factory != null)
{
if (m_factory.State == CommunicationState.Opened)
{
try
{
m_factory.Close();
}
catch
{
m_factory.Abort();
}
}
else
{
m_factory.Abort();
}
m_factory = null;
}
}
// Call the appropriate methods to clean up unmanaged resources here.
m_disposed = true;
}
}
~Proxy()
{
Dispose(false);
}
#endregion
}
This is the object factory class. The main method is CreateInstance<T>(). Based on configurations settings, the method instantiate a local or a remote object. For the proxy classes created there is a cache, so they’re created only once.
public class BusinessFactory
{
private static Dictionary<Type, Type> m_proxys = null;
private static AssemblyBuilder m_assemblyBuilder = null;
private static ModuleBuilder m_moduleBuilder = null;
static BusinessFactory()
{
m_proxys = new Dictionary<Type, Type>();
}
public static T CreateInstance<T>()
{
object obj = null;
try
{
Type type = typeof(T);
Type proxyType = null;
//Read from config if it's a remote object.
bool isRemote = ReadFromConfig(type.FullName);
if (isRemote)
{
try
{
//Search for the proxy type in the cache.
proxyType = m_proxys[type];
}
catch (KeyNotFoundException)
{
//If it's not found, create the proxy type.
proxyType = CreateProxyType<T>();
m_proxys.Add(type, proxyType);
}
obj = Activator.CreateInstance(proxyType);
}
else
{
obj = Activator.CreateInstance(type);
}
}
catch (Exception ex)
{
throw ex;
}
return (T)obj;
}
//Build a new proxy class.
private static Type CreateProxyType<T>()
{
Type type = typeof(T);
if (m_assemblyBuilder == null)
{
//Holds only one AssemblyBuilder and ModuleBuilder.
m_assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("ProxiesAssembly"), AssemblyBuilderAccess.RunAndSave);
m_moduleBuilder = m_assemblyBuilder.DefineDynamicModule("ProxiesAssembly", "ProxiesAssembly.dll");
}
//Create the TypeBuilder for the proxy class.
TypeBuilder tb = m_moduleBuilder.DefineType(type.Name + "DynamicType", TypeAttributes.Public);
//Derive the proxy class from Proxy<T>.
tb.SetParent(typeof(Proxy<T>));
//Implement interface T in the proxy class.
tb.AddInterfaceImplementation(type);
//Adds all interface methods to the proxy class.
MethodInfo[] methods = type.GetMethods();
foreach (MethodInfo method in methods)
{
//Initialize the parameter types array.
ParameterInfo[] parameters = method.GetParameters();
Type[] parameterTypes = new Type[parameters.Length];
for (int i = 0; i < parameters.Length; i++)
{
parameterTypes[i] = parameters[i].ParameterType;
}
MethodBuilder meth = tb.DefineMethod(method.Name, MethodAttributes.Public | MethodAttributes.Virtual, method.ReturnType, parameterTypes);
tb.DefineMethodOverride(meth, method);
//Generates the method's body.
//It basicaly will call Proxy<T>.BO.Method(params)
ILGenerator il = meth.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, (typeof(Proxy<T>)).GetMethod("get_BO"));
for (int i = 0; i < parameters.Length; i++)
{
il.Emit(OpCodes.Ldarg, i + 1);
}
il.Emit(OpCodes.Callvirt, method);
il.Emit(OpCodes.Ret);
}
return tb.CreateType();
}
}
Here is how the client application uses it.
Let’s assume we have the contract below and its implementation somewhere in the business tier.
[ServiceContract()]
public interface IUser
{
[OperationContract()]
int Save(BEUser user);
[OperationContract()]
int Delete(int userId);
[OperationContract()]
BEUser GetByLogon(string logon);
[OperationContract()]
BEUser GetById(int userId);
[OperationContract()]
List<BEUser> GetAll();
[OperationContract()]
int AddUserToGroups(BEUser user, List<BEGroup> groups);
[OperationContract()]
int RemoveUserFromGroups(BEUser user, List<BEGroup> groups);
}
A presentation tier could run this simple code and execute User’s methods from a local or a remote object, just depending on a configuration setting. No need of Scvutil tool.
IUser bpUser = BusinessFactory.CreateInstance<IUser>();
UPData.Users = bpUser.GetAll();
...
And if we want to close the channel as soon as possible, we can use the using { } statement.
IUser bpUser = BusinessFactory.CreateInstance<IUser>();
using ((Proxy<IUser>)bpUser)
{
...
UPData.Users = bpUser.GetAll();
...
}
References
· WCF Feature Details
o http://msdn2.microsoft.com/en-us/library/ms733103(VS.85).aspx
· Performance Improvement for WCF Client Proxy Creation in .NET 3.5 and Best Practices
o http://blogs.msdn.com/wenlong/archive/2007/10/27/performance-improvement-of-wcf-client-proxy-creation-and-best-practices.aspx