Wednesday, August 13, 2008

Programatically removing an HttpModule from SharePoint Web.config (2)

In one of my previous posts (that you can find here) I described how I managed to add a HttpModule tag to the web.config. The issue still remaining was the removal of that tag from the web.config, that I needed to do in the uninstallation process (in my case feature deactivating). At that time I used a remove tag. 

Although web.config file sees no problem in duplicate add tags, the same thing does NOT happen with the remove tags. If two or more remove tags of the same module are present in the web.config file, an error is generated - so down the drain goes our solution if two or more install-uninstall sequences occur on the same web application.

What is the solution to this problem? I found it in one of Andrew Connell's books - "Professional SharePoint 2007 Web Content Management Development" (find it in my Books sidebar). It is a fairly simple solution: at Http Module installation in a SharePoint site you add a property to the site to signal that the Http Module should be applied there. In the Http Module code behind you check if that property exists before letting the Http Module do what it is meant to do. At unistallation you remove the property, or make it null. And Voila! - although the tag in the web.config stands, the module will not act on sites unless they have the property it is looking for.

 
Posted by Madalina at 14:12:14 | Permanent Link | Comments (0) |

Tuesday, February 26, 2008

ADO NET Entity Framework Beta 3 Installation Troubleshooting

It is so frustrating to spend a whole day trying to work with a tool and not getting there because you don't know why, but apparently you are installing it all wrong...

ADO .NET EF Beta 3 has 2 patches you need to install in order to be able to use it: Beta 3 patch and EF Tools patch. Or so they say... You also need to install a VS 2008 Patch. I found this tip
here. And installed the VS 2008 patch, then the Beta 3 release and then the Tools. No luck. The ADO NET Entity Data Model Item Template was not showing up in the Add New Item screen of VS 2008. But the VS 2008 command line recognised the edmgen command. Bugger...

After a day and a thousand google searches I found the answer
here. You have to install the VS 2008 patch AFTER the Beta 3 and BEFORE the Tools.

No comment.
Posted by Madalina at 15:48:20 | Permanent Link | Comments (1) |

Thursday, October 04, 2007

Http Handlers applied – Rss Feed

RSS Feeds are very popular these days.

Recently, I found myself in the need of using a rss feed for a “Joke of the day” web part. The tricky side of the business was that I had to provide only one like in the rss feed – the joke for the current day. Many options came into my mind, but the most elegant was by far the HttpHandler one.

An HttpHandler is an independent unit that manages the server response for a given situation: for example, a specific file extension. In my case, what I need was for the web application to create a rss file “on the fly” when a user requested a certain url. The url was http://mycompany.com/RssFeed/jokes.rss and the trick - the file didn’t even exist :) . When the url was requested, I had to tell the application that a rss file (stream) had to be generated and passed to the client.

How is this done? First of all, you need a class that generated the feed. This class needs to implement the IhttpHandler interface. What this class does is to read one joke from a data source (xml, database etc) and output it to the browser as a rss file. You can put this class in a ClassLibrary project, because you will need the dll. Here is the code:

namespace MDL.RSSLibDLL

{

    public class RssHandler : IHttpHandler

    {

        public void ProcessRequest(HttpContext context) // this is the function that needs to be implemented. It comes, by dfault, with the Context of the user in the application that it handles

        {

            // Get the file name from the requested url

            string fileName = Path.GetFileName(context.Request.Path); // the rss file that the user wants

            //generally, you want to cache your rss files

string cachedChan = context.Cache[fileName] as String;

            if (cachedChan == null)

            {

                // The file is not in the cache

                string profileFolder = ConfigurationSettings.AppSettings["rssProfileFolder"];

//get the rss profile – the phyisical structure

                if (profileFolder == null)

                {

                    context.Response.StatusCode = 404;

                    context.Response.End();

                    return;

                }

                // Verify that you actually have a profile for the requested feed(a source rss file to read the structure from )

                string profilePath = profileFolder + "" + fileName;

                if (!File.Exists(profilePath))

                {

                    context.Response.StatusCode = 404;

                    context.Response.End();

                    return;

                }

   

   

                // create the rss response

                RssChannel chan = new RssChannel(profilePath, DateTime.Now);

                cachedChan = chan.GetResponse();

                double cacheTimeout;

                string appCacheTimeout = ConfigurationSettings.AppSettings["cacheTimeout"];

                if (appCacheTimeout == null)

                    cacheTimeout = 30;

                else

                    cacheTimeout = double.Parse(appCacheTimeout);

                context.Cache.Insert(fileName, cachedChan, null, DateTime.Now.AddMinutes(cacheTimeout), TimeSpan.Zero);

            }

            context.Response.ContentType = "text/xml";

            context.Response.Write(cachedChan); // output the rss to the browser

   

           

        }

   

        public bool IsReusable

        {

            get

            {

                return true;

            }

        }

   

    }

   

    internal class RssChannel //my class that implements a Rss feed item

    {

        private string _title;

        private string _link;

        private string _description;

        private string _language;

        private string _date;

   

        private string _profilePath; //resulting RSS structure

        private ArrayList _items;

   

        public RssChannel(string profilePath, DateTime date)

        {

            _profilePath = profilePath;

   

            _date = date.Month.ToString() + "/" + date.Day.ToString();

            Load();

        }

   

   

        public string Title

        {

            get { return (_title); }

            set { _title = value; }

        }

   

        public string Link

        {

            get { return (_link); }

            set { _link = value; }

        }

   

        public string Description

        {

            get { return (_description); }

            set { _description = value; }

        }

   

        public string Language

        {

            get { return (_language); }

            set { _language = value; }

        }

   

        public RssItem this[int index]

        {

            get

            {

                if (index < _items.Count)

                    return ((RssItem)_items[index]);

                return (null);

            }

        }

   

        public string GetResponse()

        {

            XmlElement workNode;

            XmlElement itemNode;

            RssItem currentItem;

            XmlDocument rssDocument = new XmlDocument();

            // Create the rss document node and set the its attributes

            XmlElement docNode = rssDoc.CreateElement("rss");

            XmlAttribute attr = rssDoc.CreateAttribute("version");

            attr.Value = "2.0";

            docNode.Attributes.Append(attr);

            rssDoc.AppendChild(docNode);

            // Create the channel element and its children

            XmlElement chanNode = rssDoc.CreateElement("channel");

            docNode.AppendChild(chanNode);

            workNode = rssDoc.CreateElement("title");

            workNode.InnerText = _title;

            chanNode.AppendChild(workNode);

            workNode = rssDoc.CreateElement("link");

            workNode.InnerText = _link;

            chanNode.AppendChild(workNode);

            workNode = rssDoc.CreateElement("description");

            workNode.InnerText = _description;

            chanNode.AppendChild(workNode);

            workNode = rssDoc.CreateElement("language");

            workNode.InnerText = _language;

            chanNode.AppendChild(workNode);

   

            // Add item nodes to the channel

            for (int index = 0; index < _items.Count; index++)

            {

                currentItem = (RssItem)_items[index];

                itemNode = rssDoc.CreateElement("item");

                chanNode.AppendChild(itemNode);

                workNode = rssDoc.CreateElement("joke");

                workNode.InnerText = currentItem.Joke;

                itemNode.AppendChild(workNode);

                workNode = rssDoc.CreateElement("date");

                workNode.InnerText = currentItem.Date;

                itemNode.AppendChild(workNode);

   

            }

            return (rssDoc.OuterXml);

        }

   

        private void Load()

        {

            _items = new ArrayList();

            

            // Set channel properties from the profile:

            _title = profile.SelectSingleNode("rss/channel/title").InnerText;

            _link = profile.SelectSingleNode("rss/channel/link").InnerText;

            _description = profile.SelectSingleNode("rss/channel/description").InnerText;

            _language = profile.SelectSingleNode("rss/channel/language").InnerText;

            string _source = profile.SelectSingleNode("rss/channel/source").InnerText;

   

            // Retrieve items from the joke source

// input your code depending on the datasource

           

            RssItem currentItem = new RssItem( /*joke text field*/,/*joke date field*/);

            _items.Add(currentItem);

           

                             

        }

            // internal class that implements the Joke object

        internal class RssItem

        {

            private string _joke;

            private string _date;

   

            internal RssItem(string joke, string date)

            {

                _joke = joke;

                _date = date;

            }

   

            public string Joke

            {

                get { return (_joke); }

                set { _joke = value; }

            }

   

            public string Date

            {

                get { return (_date); }

                set { _date = value; }

            }

   

        }

   

    }

This class uses 3 (or more) pieces of information that you need to provide from an external source:

  1. The rss profile file – the file with the rss structure
  2. The cache timeout value
  3. The data information – xml file path, database connection string, select query etc

My code used an xml file that had jokes for all the days of the year. What I did was to get the current date and then read from the xml file the corresponding joke.

You can pass this information in a very elegant way – the web.config file. The web config file of the web application that will serve the rss. This web application can have no pages at all – that is the beauty of it J. What you need to do for the web application is:

  1. Put this in the web.config:

The HttpHandlers section tells the application that the handler for the rss files is the RssHandler class.

  1. Add the Classlibrary application dll in the bin directory
  2. Tell IIS that you want to handle the rss file requests differently.

 

You can copy the executable path from another extension mapping.

Now all the requests for a rss file will be handled by the RssHandler class. The beauty of it is that you can particularize the class for different rss files.

Here is a sample structure for the rss file:

 

If you build this solution and install it on your localhost, if you then go to http://localhost/my_app_name/jokes.rss you should be shown a feed with one item for the corresponding date.

Posted by Madalina at 18:00:17 | Permanent Link | Comments (0) |

Monday, September 24, 2007

.NET for every-day-useful tasks :)

What do you know, it pays off to be a .NET developer :) You, of course, knew that you can develop applications for your pocket PC, but let's face it, did you ever really need to do that for your own personal pocket pc phone? Nop. There are so many freeware tools out there, that it would really be a waste of time to sit down and write them yourself. But this time... maybe it's worth it. This link describes a user scenario that we all find ourselved in all the time - the loud volume of your phone ring tone. As we walk on the street with out MP3 player in our years or in the car with the radio loud enough to cover the noisy city, we have the ring volume at the maximum level, isn't it? All good, untill we find ourselves in a meeting with the boss (and usually the rest of the colleagues) in a big or small room (size doesn't matter really). And there it goes - the phone rings.

Wouldn't it be great for our super cool phone to be as smart as us ;) ? And to adjust the volume of the ring tone according to the surrounding noises (or lack of) ? The MSDN article describes the solution.

"When .NET rocks, developers"... adjust their ring tones :)

Posted by Madalina at 16:46:34 | Permanent Link | Comments (0) |

Monday, September 17, 2007

List.FindAll

Generic lists are very powerfull 2.0 tools. If you use them, try to take advantage of every property they have. For exemple, instead of using a foreach statement to find one or more items in a list, use the Find() and FindAll() functions.

The Find All() function is used when we want to retrieve all the ietms in a list that have a certain property. Let's suppose that we the next class:

public class QuarterlyReport{

#region Private Properties

private string _complaintType;

private string _productClass;

private string _incidentGrade;

#endregion

#region Public Properties

public string ComplaintType

{ get{ return _complaintType;}}

public string ProductClass

{get { return _productClass; }}

public string IncidentGrade

{get { return _incidentGrade; }}

#endregion

...

public static List<QuarterlyReport> GetAll(...)

{ ... }

}

The GetAll() method returns all the Report objects from the database.

Sometimes you need to avoid querying the database several times. For example, the getAll() method might return objects that have diferent ComplaintType attribute values. How do you use the FindAll() method to get, from the returned list, the reports that have ComplaintType = "X" ?

First, you build a class that will serve as an implemetation of generic Predicate:

public class ReportMatcher

{

string type;

public ReportMatcher(string _type)

{

this.type = _type;

}

public bool Test(QuarterlyReport rep)

{

if (rep.ComplaintType.Equals(type) )

return true;

else

return false;

}

}

Then, all you need to do in code is to use this class as your filter:

List<QuarterlyReport> allReports = QuarterlyReport.GetAll(...);

ReportMatcher rm = new ReportMatcher("X");

List<QuarterlyReport> rep = reportErrors.FindAll(rm.Test);

If you need, you can add more preoerties to the ReportMatch class and, this way, more filters to the search.

This method also helps if you are using DataSet.Select and want to bind the result of the Select() to a data object. You can read the dataset into a generic list and then search it with FindAll().

 

Posted by Madalina at 16:04:55 | Permanent Link | Comments (0) |

Wednesday, September 05, 2007

Maintaining page vertical position at postback

If you search this in Google, you will get tons of answers that give a javascript solution - more or less complicated. If you are working on an ASP NET application, the solution can be simple and straight-forward:

Page.MaintainScrollPositionOnPostBack = true;

You can also use the SmartNavigation property, but this one is marked as deprecated.

Posted by Madalina at 11:38:11 | Permanent Link | Comments (0) |

Wednesday, June 20, 2007

Enterprise Library and Oracle

Enterprise Library is a very useful framework that Microsoft gives away for free :) If you build a .NET application, of course ;)

Enterprise Library Data Access module offers an alternative to a self-written data access layer. And, as suspected, it doesn't only support Microsoft SQL, but also Oracle and other databases. Unfortunately, this is pretty much what you get in the Enterprise Library documentstion about Oracle connections - it can be done.

I first started with the EL Configuration Tool - easy to use and up front. The result was a web config file that I added to an ASP .NET application and then ran it. Surprise - it didn't work :). The error was "... an Oracle 8.* Client must be installed on the machine". What the documentation of the EL didn't say was that, in order to connect to Oracle, if the application is NOT running on the same server that Oracle is installed, then you need to install the Oracle Client Tools on the application server. The Client Tools install drivers that the .NET framework needs in order to access the database, and thus, the data. So I installed Oracle Client tools on my machine, according to what MSDN says here. I ran the application - error. Same error. I restarted the computer ( Cool ) and ran the application again - error.

But to end your pain :) here is the solution:

SCENARIO: .NET Application with Oracle Database. The two are installed on diferent servers. What you have access to is the application server.

SOLUTION: the server running the application must have installed:

  1. Oracle Client Tools
  2. Oracle Data Provider for .NET

The application web.config file must contain the section:

< add databaseType="Microsoft.Practices.EnterpriseLibrary.Data.Oracle.OracleDatabase, Microsoft.Practices.EnterpriseLibrary.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null" name="System.Data.OracleClient" / >

Posted by Madalina at 09:48:26 | Permanent Link | Comments (0) |

Tuesday, June 12, 2007

Atlas and Custom Errors

If you use Atlas versions of ASP .NET AJAX, it is a known fact that you cannot redirect the user server-side from an event raised by a control that is in an Update Panel. We can find ways to deal with this problem, but what happens in the particular case of Errors?

Error handling code often implies using Application_Erorr function in Global.asax and automate redirection to a page where an error message is posted - "An error has occurred. The administrator has been notified" is my favourite :) (with DB logging of course)

If an error occurs within a post-back triggered by a control in an Update Panel, we may be surprised to see that the user is not redirected anywhere and the error is shown in an alert message box.

Solutions? First of all, let the error be handled by the Application_Error code. For that you can use one of the Script Manager events. There you just throw the exception:

Protected Sub ScriptManager_PageError(ByVal sender As Object, ByVal e As Microsoft.Web.UI.PageErrorEventArgs) Handles ScriptManager.PageError   

  Throw e.Error

End Sub

Second, you would also like the redirect to work - unfortunately, only .NET Ajax offers a solution for that - the Script manager has a property called AllowCustomErrors that, when set to true, allows the application to handle errors through the Custom Errors settings in web.config. More details here.

Posted by Madalina at 10:19:44 | Permanent Link | Comments (0) |

Monday, April 16, 2007

Encrypted connection strings and Enterprise Library

A very important aspect of a web application is security. We use authentication, encrypted passwords, SSL and so on. But what we all have in out web.config file is a Connection String - with a password in it. There are two ways to minimise this security breach:

  1. use integrated security, so that the connection string looks something like: add key="DBConnStr"      value="server=(local);Integrated Security=SSPI;database=MyDB"

  2. encrypt the connection string

The first option seams safe, but the application will use the system user to access the database, which is not that secure after all.

The second option is the best choice. The query string would look something like this:

key="DBConnStr" value="8cEr3iC2W9ITIqNm4UA5YH7HfrGiZzA5a6acrx4G"

You would have to decrypt the connnection string each time you use it.

But if you use Enterprise Library for the data access layer, this option is not usable. So, what is to be done? Fortunately, Microsoft provides us with the possibility of having encrypted keys that EL can read. And methods that do the job.

Before:

< add name="DefaultDB" connectionString="Data source=sql01; Initial Catalog=MyCat; User Id=sa; Password=pwwd; " providerName="System.Data.SqlClient" / >     

Actions:

1. edit web.config by adding the following section:                        type="System.Configuration. RsaProtectedConfigurationProvider , System.Configuration, Version=2.0.0.0, Culture=neutral,  processorArchitecture=MSIL" keyContainerName= "connectionTestKey" / >    

2. generate a new RSA cryptographic key container:

aspnet_regiis -pc "connectionTestKey" -exp

This will create a new machine level key container in the following location:

C:/ Documents and Settings/ All Users/ Application Data/ Microsoft/ Crypto/ RSA/MachineKeys

Make sure the ASPNET account has read access in this directory.

3. grant access to the key container aspnet_regiis -pa "connectionTestKey" "ASPNET"

4. Encrypt the section of your web.config file

aspnet_regiis -pe "connectionStrings" -app "/virtual_directory_name"

After:

 

Posted by Madalina at 16:19:36 | Permanent Link | Comments (0) |

Wednesday, March 28, 2007

Cross Page Posting ASP .NET - alternative to long and complicated query strings

Scenario: in my application, I have a page where I search for records. Let's say user accounts. I type the user name in a TextBox and hit Search. the results are displayed. If there is no available result, I want an 'Add New User' button to appear on the page, button that redirects the user to the page where you can insert new users and fill in the fields with the info i searched for in the previous page.

I don't like large and more or less explicit querystrings. That is why for the Add New user burron I will not perform Response.Redirect("NewUser.aspx?username=" + txtUsername.Text). Plus, what if I have 10 fields on the search page? The length of the query string has a limit and so does my focus :)

The solution is easy: I should just redirect to New User and read the information from the Previous Page.

Zis si facut. (word by word translation not supported :) )

There are 4 steps:

1. the Add New user Button:
 < a s p : LinkButton ID =" lnkAdd" runat ="server" Visible ="False" PostBackUrl= "~ /NewUser.aspx" > Add New Patient < / asp : LinkButton>
2. the Search page code: add a public property that exposes the control(s) you want to look for when you are in the next page:
    public TextBox TxtName
    {
        get { return txtName;  }
    }
 

3. the New User page:
 < % @  PreviousPageType VirtualPath=" ~ / Search.aspx "  % >
4. the code
 if(PreviousPage.Title.Equals("Search"))
 {
            string name = PreviousPage.TxtName.Text;
 }

Posted by Madalina at 14:45:33 | Permanent Link | Comments (0) |
1 2 3