In part I, I talked about how I had a need for a way to managed suspended messages in BizTalk using something other then the BizTalk Administration tool. Using a combination of BizTalk dll’s I was able to query, read and suspend BizTalk messages. The need for a “web” based tool is one of accessibility to our BizTalk server from remote machines that are not directly connected to the DMZ zone that they reside in. I’ve also been wanting to create a tech support tool for myself to manage other aspects of our hosted solution, and this would fit in nicely with it.
Being a glutton for punishment, I decided to sacrifice my weekend and go for the more complex, but really cool sounding idea of a set of web services that interface with SilverLight 2. I choose SilverLight as I’ve been wanting to get my hands dirty with it for some time, and another member of the team was talking about doing the same, and what’s a little friendly competition? While I had a grand vision of what my tool would look like, which I will refer to as the Unified Administration Tool (UAT), I knew I wouldn’t be able to code everything in one weekend. I had to set some realistic goals and try to add only what was needed, while still allowing for future functionality to be “plugged-in”. The good thing was, since this was pretty much just for me, I didn’t have to worry about other peoples requirements ;)
The UAT is an n-tier application which will provide administrative functionality over our hosted solution that is distributed among several servers in different DMZ zones (as mentioned in Part I of the BizTalk Web Admin). In addition to dealing with the distributed nature of our servers, the tool must also account for the various environments, with one environment consisting of an instance of our solution deployed across one or more servers. Perhaps to put this more simply is that I wanted to be able to perform the same functions on our development, staging, demonstration and production environments.
I started out by created a new project folder to which all my various Visual Studio projects will be added. I moved my BizTalkUtilities projects into the Components folder as shown below, and then started adding the other components and UI projects as needed. Below is an overview of how I have my solution folder setup, which will probably change over time. The idea is that Components are reusable across all the UI and Services, and will probably have some more domain specific wrappers. Controllers under UI will be re-usable across each of the UI projects and provide an interface between the components and the UI.
- UnifiedAdmin
- _Solutions
- Components
- BizTalkUtilities
- BizTalkUtilitiesTest
- Documentation
- Scracth (Prototypes and other throw away code used for quick tests)
- BizTalkMessageBrowser (Test win forms app for BizTalk Utilities)
- Services
- UI
- Controllers
- Silverlight
- SilverlightMobile (Future project)
- WPF (Future Project)
I decided that I will use the Entity Framework as my “ORM”, combined with Ado.Net Data Services as my primary web services mechanism. Combined with Silverlight, I’ll get to tackle three new technologies at once, and either learn allot, or give up, ultimately frustrated, wishing I would have chosen a few older tried and true technologies. Before we even get to the UI, I needed to start setting up my services and entity model. I have a feeling I’ll be re-arranging some of these projects, but for now I have a class library called Entities under Components, and a web project called UnifiedAdminService under Services.
I really don’t know what the best practice way for organizing my entity models are, and it seems like one entity model per project seems a bit of an over kill, however, putting all the entities I will be working with in the same project doesn’t seem to smell good either, as there defiantly a clear break between them. I started out with an entity model for a database called Utilities, which will be the “glue” that holds my Unified Admin tool together. It’s sort of a catch all database that currently contains tables for some SQL based monitoring I have setup, as well as users (who can access the UAT), and servers (what servers does the UAT work with). I created a new Ado.Net Entity data model and had it auto generated the model from the database schema. This was pretty easy and straight forward. I prefix all my table names in my schema with various prefixes for grouping and identification, such as “mon_t” which means the table is used for the monitoring functionality, and it’s a table. I don’t want these prefixes in my code, so I am going to rename the entities in the model. To start with I’m only renaming a couple until I see how the EM is updated and used throughout the code.
Next I created a new Ado.Net data service in the web project. This created a new .svc file with a code behind file that inherits from DataService<T>. I updated T to reference my entity model (Entities.UtilityEntities) and also configured the security to allow read access for entities. You do this by using the config.SetEntitySetAccessRule method in InitializeService.
public class TsiUtility : DataService<Entities.UtilityEntities>
{
// This method is called only once to initialize service-wide policies.
public static void InitializeService(IDataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
}
}
Even thought the auto-generated comments for InitializeService make it sound like it’s only called once in the life-cycle of an AppDomain, I wasn’t sure. This post talks a little bit about the InitializeService method, and does in fact point out that the InitalizeService method is only called once.
I attempted to browse to my newly created service and was greeted with an un-informative error, “The server encountered an error processing the request. See server logs for more details.”. Since I’m running the development server that’s included with Visual Studio, there are not much in the way of server logs (that I could find anyway). Jumping into debug mode showed that the error was caused by an incorrectly configured connection string. The connection string used my the Entity Framework is not your standard connection string, so you need to make sure that you copy the connection string created for you in your Entities project to your web.config in your web project (assuming your entity model is in a separate project). I was trying to use the Server, Database, Trusted_Connection syntax, and apparently that’s not OK. Example connection string:
<add name="TsiUtilityEntities" connectionString="metadata=res://*/TsiUtility.csdl|res://*/TsiUtility.ssdl|res://*/TsiUtility.msl;provider=System.Data.SqlClient;provider connection string="Data Source=Server;Initial Catalog=Utility;Integrated Security=True;MultipleActiveResultSets=True"" providerName="System.Data.EntityClient" />
Once I had the correct connection string, I was able to navigate my entity model using just my browser. Of course I haven’t done any type of authentication, and I don’t want to have just anybody browse to the URL for the UAT and start browsing, so it’s time to add some security. Ado.Net data services makes use of existing security provides as long as they set the HttpContext principle, so you can use HttpContext.Current.User.Identity.Name and then compare that against records returned by the EM. Something that I didn’t fully grasp at first, was that by the time Ado.Net Data Services “takes over” the user should have been authenticated, so there really is no “login” event in Ado.Net data services where you can set additional information.
For simplicity, I decided to use Integrated Authentication, and store additional information after the user is authenticated. Since this is a regular asp.net application, you can add a Global.asax, and add code to the AuthenticateRequest event handler to perform additional security related code. When AuthenticateRequest is called, whatever mechanism that is configured to handle authentication has finished, and there should be a value in HttpContext.Current.User. What I did was user the Name property to query my users table that was part of my entity model and cache the results in a hash table, which itself is stored in the application context. If the user does not exist in the database, I set the Current.User to null, and cache a null value with the key of the user name that was authenticated.
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
string userName = HttpContext.Current.User.Identity.Name;
//Get a collection of cached users. If it doesn't exist create and cache it.
System.Collections.Generic.Dictionary<string, Entities.User> users = (Dictionary<string, Entities.User>)HttpContext.Current.Cache["Tsi_Users"];
if (users == null)
{
users = new Dictionary<string, Entities.User>();
HttpContext.Current.Cache.Add("Tsi_Users", users, null, DateTime.Now.AddHours(1), System.Web.Caching.Cache.NoSlidingExpiration,
System.Web.Caching.CacheItemPriority.Normal, null);
}
Entities.User user = null;
//Check to see if we have a cached user for this username. Use ContainsKey as we will store
//a null value to indicate a user is not authorized.
if (users.ContainsKey(userName))
user = users[userName];
else
{
Entities.TsiUtilityEntities entities = new Entities.TsiUtilityEntities();
user = entities.UserSet.FirstOrDefault(u => u.WindowsUserName == userName && u.IsEnabled == true);
user.ua_tx_UserServers.Load(); //load servers this user has access to.
}
//User doesn't exist in the database either, so un-authenticate them
//allow to continue thru so we add a null object with the username to cache
if (user == null || HttpContext.Current.User.Identity.IsAuthenticated == false)
{
//When this is set to null, this method should finish, but no data will display.
//Seems like aspx pages will still display though. However if you check the CurrentUser
//it will be unathenticated.
user = null;
HttpContext.Current.User = null;
}
//save to users collection. Double check to make sure it wasn't added somewhere else.
if(users.ContainsKey(userName) == false)
users.Add(userName, user);
}
What I have not decided upon, nor have I done the research into, is if I should be caching the DataContext in the application or session cache for the user.
So where does this leave me? Well, I certainly didn’t get even close to what I wanted to accomplish in one weekend, as I haven’t even touched SilverLight yet. I do have some basic authentication, but that’s more using traditional asp.net then doing anything special with Ado.Net data services. From the looks of it, I will have to add QueryInterceptors to every entity I wish to do security on, which seems like a pain and allot of extra un-necessary work. Next time I hope to accomplish the following:
- Research to find out if you should cache the data context
- Write out security requirements and research the best way to implement in Ado.Net Data Services
- Figure how I am going to have one set of services that can connect to multiple databases (assuming the database schemas are kept in sync).