WebMasterSam

.Net, SEO, Dynamics CRM, AdSense/AdWords, Dating sites, Silverlight, Web hosting and more

About me

I'm an IT consultant working primarily with the .Net Framework as a developper and architect. I also work on my own on my personnal dating websites. I've been developping websites since 2000.

If you like what I do, feel free to support me

PayPal - The safer, easier way to pay online!

Bookmark

Bookmark and Share

Last comments

None

Changing Microsoft CRM Dynamics 4.0 Encoding

Recently I worked on a project involving Microsoft CRM Dynamics 4.0 and because the application was in french we decided to change the encoding from UTF-8 to ISO-8859-1 which is quite simple to do but we ended up with a lot of problems...

If you change the encoding of the CRM in the web.config (globalization tag), CRM will then start to show you weird things (horrible characters) instead of accents. The reason is simple, if you convert a text (containing accents) from an encoding like ISO-8859-1 (or Windows-1252) to URF-8 all the accents will be replaced with fucked up characters, leaving normal non-accentuated characters intact.

I discovered that CRM seems to have parts that are always running under UTF-8 instead of following the encoding in the web.config file, so this cause a constant-encoding-converting-glitch. I did not found a solution to solve those CRM glitches so I decided to come back to default CRM encoding: UTF-8.

Beeing in UTF-8 causes convertion problems to the files I add to the CRM (.js - JavaScript). Because my files I create are in Windows-1252 (default), when CRM flush them to the client, they get "converted" and so the accents are beeing mashed up, this causing IE to not load those .js files. The only solution for this is to manualy change the encoding of the .js files to UTF-8 so that they don't get converted automatically.

Conclusion

Never change the CRM encoding; let it to UTF-8 and adapt your stuff.

Solution for "The server committed a protocol violation: Section=ResponseStatusLine"

If you get an error like "The server committed a protocol violation", you may have some of the following problem (which can be solved by one of the provided solution as well).

Unsafe header parsing

Unsafe header parsing is an option you can turn on on your ASP.Net website (in the web.config) to allow the framework to parse responses. But what is an unsafe header ? It is a header in which the keys contains one or more spaces (that is not allowed in the HTTP 1.1 specifications).

The common case is having a space in the "content-length" header key. The server actually returns a "content length" key, which, assuming no spaces are allowed, is considered as an attack vector (HTTP response split attack), thus, triggering a "HTTP protocol violation error" exception.

To allow the parsing of unsafe headers, add the following to your web.config :


<system.net>
   <settings>
      <httpWebRequest useUnsafeHeaderParsing="true" />
   </settings>
</system.net>

You forgot to allow HttpGet and/or HttpPost on your web.config

If you call a WebService, you must accept the HttpGet and/or HttpPost protocols in your web.config (they are disabled by default).

So add the following to your web.config file :


<configuration>
  <system.web>
    <webServices>
      <protocols>
        <add name="HttpGet"/>
        <add name="HttpPost"/>
      </protocols>
    </webServices>
  </system.web>
</configuration>

You're using Skype

If you use Skype, make sure to uncheck the option for using port 80 and 443.

None of the above: find it yourself

If this has not helped, use the following links to help you find the answer :

Configure network tracing : http://msdn2.microsoft.com/en-us/library/ty48b824.aspx

Interpreting a network trace : http://msdn2.microsoft.com/en-us/library/46fcs6sz.aspx

Posted: Jul 03 2009, 15:37 by WebMasterSam | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: .Net | Programming
Social Bookmarks: E-mail | Kick it! | DZone it! | del.icio.us

Adding custom error message to ValidationSummary without validators

Validators are a great thing in the ASP.Net world (as well as the .Net WinForms world...), but sometimes they don't suffice. For example, if you want to validate something not related to a control on the page or something very complex you can't achieve even with the custom validators, you may want a particular solution like this one.

First approach : using the CustomValidator

With this approach you simply have to do the following piece of code when you want to add a custom message to your validator :


CustomValidator val = new CustomValidator();

val.IsValid = false;
val.ErrorMessage = "Custom error message to add to the ValidationSummary";

this.Page.Validators.Add(val);

This is a cool, clean and simple way to add custom error messages to the ValidationSummary.

Second approach : creating a validator by implementing IValidator

The .Net framework allows you to create very custom validator by implementing the IValidator interface. So you create the validator and then use the same technique I've just showed you before, just like this :


public class ValidationError : IValidator
{
    private ValidationError(string message)
    {
        this.ErrorMessage = message;
    }

    public void Validate()
    {
        this.IsValid = false;
    }
}

this.Page.Validators.Add(new ValidationError("Your custom error message"));

Personnaly I prefer this one because you are completely custom and you can add messages in a single line of code.

Posted: Jul 03 2009, 15:15 by WebMasterSam | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: .Net | Programming
Social Bookmarks: E-mail | Kick it! | DZone it! | del.icio.us

Culture ID 2155 (0x086B) is not a supported culture

I recently came across a VERY annoying problem on my personnal (this one) BlogEngine.Net blog.

What happened is that I installed some Windows Updates (a bunch) on my Windows Server 2003 server and then, every time I accessed an article of this blog I received this error message : Culture ID 2155 (0x086B) is not a supported culture. What a real annoying problem !!

I don't know the exact reason but what I know is that cultures are OS specific, so the Windows Update is the cause... it's not related to the .Net Framework so don't waste your time trying to reinstall the framework, it will not work.

The Solution...

Resintall the Service Pack 2 for your Windows Server 2003.

TreeNode bolded text gets cropped (clipped) in the .Net TreeView

If you use the TreeView that comes with the .Net Framework, you probably hit the following problem :

You add bold elements because you want some formatting, and what you see is that your bolded elements gets cropped. You try to set the NodeFont before and after setting the text of the node but nothing seem to make a change, so you decide to Google this and find this MSDN article (TreeNode.NodeFont property) that says in a note, "If the node font is larger than the Font property value set in the TreeView control, the tree node label text is clipped".

So what to do ? Simple solution !

You set the NodeFont property of the TreeView to a bolded font and then, when you add your nodes to your TreeView, you set the NodeFont property (of the nodes) to a non-bolded font (except for the bolded elements). You will then see what you expected :

Posted: Jun 02 2009, 12:33 by WebMasterSam | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: .Net | Programming
Social Bookmarks: E-mail | Kick it! | DZone it! | del.icio.us

Rent a coder; does this really work ?

If you're a programmer, you certainly already created a little program or website for someone and get paid for it. This is a normal thing; you have a talent that some others don't have and you can help them, charging something for your time and effort.

On the web, there is this kind of thing too. You can pay someone for a specifig job you want to be done. There is websites that exposes this principle like RentACoder and GetACoder. These websites look great, there is a lot of projects awaiting for bids and plenty of coders ready to code ! Fine, this seems to be a great place to search for custom jobs or to post a project you don't the time (or knowledge) to do by yourself.

So, let's take a look to both sides of the medal...

Being a coder, bidding on projects

When you signup on websites like RentACoder, you can search for projects you want to realize. There is plenty of different categories: .Net, Java, SQL Server, MySQL, graphic design, bug fixing, etc... so you can find a perfect project for you and bid on.

Now that you've found the project you start to bid on and you think about a price (between the price range). You than have your price in mind but you find that 50 indians have already bidded on this for 10% of the price you thought of charging... Damn ! This is the reality, in india, being paid 5$/hour is big. Here, if we work as a freelancer we can easily charge 40$-100$ per hour. Every single project you will find will have indian bids on it. Maybe the project manager don't want cheap indian code for a fraction of the price, but wants someone who will charge a lot more but do a great job so with those projects, you may have a chance on winning it.

If you win the project, you will have to realize it so you will need specifications, you will need to talk to the project manager often to really understand his needs. This can be hard. Remember what you do at your day job... you're near from the client, you ask questions anytime, you do meetings, you chat, etc... The communication is easy, but if the client is about 1000km away, will you spend hours on phone ? Paying bills will kill all the profit you can make out of the project itself. Will you chat everyday ? Chatting is good for easy questions, but don't make serious decisions using MSN. Will you have the opportunity to meet up with the project manager ? Maybe not if he lives far from you, and I'm sure no one of you will pay 200$ for a plane ticket on a 400$ project !

There is also another thing that may scares some people... will you really get paid for you work ? When you give your source code, the project manager can stop talking to you, stole your work and don't pay a dime for it... it's a danger. I don't know if this happens often but, it's not risk-free.

Being a project manager, awaiting for bids

Being a project manager is not very different from being a coder because the real problem with coder rental is the communication, and this is a problem for both sides. Even if this problem is the same, there is some other things that are different... Like I said previously, there is a lot of indians that want to work for peanuts so you will be able to buy something that fits your needs for a small amount of money.

So you decide to take someone for your project (whoever he his and where he's from). You send him the specifications of the work to be done and he starts to work on it. You communicate everyday to have a feedback about the way the developpment is going... eveything seems to be fine.

Then, you get your work, you pay for it and do what you have to do with your project (put it on the internet, use it on your intranet, etc...). Everything seems to be fine but soon you discover some bugs and you open the code... What you find is not, and absolutely not following any existing programming best practices. You're not able to follow the code logic (if there is a logic...), you find weird things and... Damn it ! You can maintain this code without rewriting a big part... This is normal, you can't control someone else's code. Personnaly at my day job I check my coworkers code barely every day and find things that I tell them to change or fix it the best they can, but I will never be satisfied until I do it myself.

Conclusion...

Personnaly I think that you can't rent a coder... at least 90% of the time. I think sometimes you can rent someone to create graphical content or database design, but I don't think you can have success with programming. So in general, I don't think it's a good idea being a coder and not a good idea being a project manager (on rent a coder).

Posted: Apr 30 2009, 16:29 by WebMasterSam | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Social Bookmarks: E-mail | Kick it! | DZone it! | del.icio.us

What is a .XAP file (SilverLight) and what does it contain

If you already created a Silverlight application you have certainly noticed that all you do in your Silverlight project will be packaged as a XAP file that you will then put on your website.

What is a XAP file ?

The XAP file is a package that contains everything your Silverlight application needs to run on the client browser.

What does it contain ?

All the assemblies (DLL) you created, all the Framework assemblies you referenced in your application (like System.Xml.Linq, System.Windows.Controls and some others) and a manifest file, named AppManifest.xaml. It also contains culture-specific assemblies (if you translated your application in some other languages).

What does XAP stands for ?

You have to pronounce "ZAP" and it stands for "Silverlight Application Package". I don't know why MS didn't called it SAP instead of XAP but... this is it's meaning !

Can I open a XAP file ?

Oh sure you can, it's only a ZIP file. You can rename it an open it with WinRar or WinZip.

How can I create a XAP file ?

You build your Silverlight project ! Simple, no ?

Why does my Silverlight application is packaged in a XAP file ?

There's 3 main reasons for this :

  1. Because all the content can be compressed within the zip file so you save bandwith and time
  2. Because DLLs are prohibited from downloading (by default)
  3. Because it's easier to download one file than "72" (any number) files
What does the AppManifest.xaml looks like ?

This file is the main entry point in the package for the Silverlight client. When Silverlight extracts the content of the XAP package it reads the AppManifest.xaml to know which assembly it has to load.


<Deployment EntryPointAssembly="SilverlightApplication1">
  <Deployment.Parts>
    <AssemblyPart x:Name="SilverlightApplication1" Source="SilverlightApplication1.dll" />
    <AssemblyPart x:Name="Silverlight.Trace" Source="Silverlight.Trace.dll" />
    <AssemblyPart x:Name="System.Windows.Controls" Source="System.Windows.Controls.dll" />
    <AssemblyPart x:Name="System.Xml.Linq" Source="System.Xml.Linq.dll" />
    <AssemblyPart Source="en/SilverlightApplication1.resources.dll" />
    <AssemblyPart Source="fr/SilverlightApplication1.resources.dll" />
    <AssemblyPart Source="fr/System.Xml.Linq.resources.dll" />
    <AssemblyPart Source="fr/System.Windows.Controls.resources.dll" />
  </Deployment.Parts>
</Deployment>

How do I use a XAP file ?

You ? You don't use this file; Silverlight does. The only thing you have to do with it is copy it to your ASP.Net application.

Where do I deploy my XAP file ?

If you want to do it the common way, deploy it to the ClientBin folder. You can deploy it wherever you want but, I suggest you put it in the ClientBin folder.

Complete .Net COM+ interop class (COMAdmin wrapper)

If you already worked with the Interop.COMAdmin, you certainly said a lot of bad bad words ! Don't worry, you're not alone. Recently a friend of mine has to create an application that can export security from COM+ and then reimport it on another server. Because I love to do very technical code I decided to write a complete COM+ interop class that wraps the Interop.COMAdmin one. By doing this, my friend had a clean interop do deal with and so the tool to export security has been realized very quickly.

I decided to write a really clean interop so I spent about 3 days of work just to write this interop because no one on the internet has took the time to create it ! I want to share it with you because it can make you save a looooooooooot of time and frustration. You can download the code right here : ComPlusInterop.zip (31.45 kb).

The architecture behind that interop is as follow; there is a base class, COMElementBase, that wraps the access to the COMObject and there is derived classes matching the different things in COM+ such a applications, components, roles, methods and interfaces. Here is a list of the different classes the interop has :

  • ComponentServices
    • Represents the COM+ instance (can be from a remote server)
  • COMElementBase
    • The base class for the following classes
  • COMApplication
    • Represents an application
  • COMComponent
    • Represents a component (DLL)
  • COMInterface
    • Represents an interface of a component
  • COMMethod
    • Represents a method of an interface
  • COMRole
    • Represents a role definition
Because everything in COM+ is retrieved by string, I have a lot of constant values in each different class; each constant matches the internal property name. To retrieve a property value or to set one, the derived class (such as COMApplication) calls an internal method of the COMElementBase class. To simplify the process of casting property values, I created several methods in the base class to retrieve property values like GetPropertyValueBoolean, GetPropertyValueInteger and so on...

To show you in code what it looks like, here's the COMComponent class :


using Interop.COMAdmin;

using System.Runtime.InteropServices;

public class COMComponent : COMElementBase, IComparable
{
    #region "Members"

        private ReadOnlyCollection<COMInterface> _lstInterfaces;
        private ReadOnlyCollection<string> _lstRoles;
        private COMApplication _parent = null;

        private COMAdminCatalogCollection _colInterfaces = null;
        private COMAdminCatalogCollection _colRoles = null;

        private bool _dispose = false;

    #endregion

    #region "Constantss"

        internal const string NUMBER_ERROR_VERSION_INVALID = "0x8011042C";

        internal const string KEY_COLLECTION_INTERFACE = "InterfacesForComponent";
        internal const string KEY_COLLECTION_ROLES = "RolesForComponent";

        internal const string PROPERTY_NAME = "ProgID";
        internal const string PROPERTY_GUID = "CLSID";
        internal const string PROPERTY_DESCRIPTION = "Description";
        internal const string PROPERTY_DLL = "DLL";
        internal const string PROPERTY_MAJOR_VERSION = "VersionMajor";
        internal const string PROPERTY_MINOR_VERSION = "VersionMinor";
        internal const string PROPERTY_BUILD_VERSION = "VersionBuild";
        internal const string PROPERTY_SUBBUILD_VERSION = "VersionSubBuild";
        internal const string PROPERTY_TRANSACTION = "Transaction";
        internal const string PROPERTY_COMPONENT_TRANSACTION_TIMEOUT_ENABLED = "ComponentTransactionTimeoutEnabled";
        internal const string PROPERTY_COMPONENT_TRANSACTION_TIMEOUT = "ComponentTransactionTimeout";
        internal const string PROPERTY_ACCESS_CHECKS_ENABLED = "ComponentAccessChecksEnabled";
        internal const string PROPERTY_POOLING_ENABLED = "ObjectPoolingEnabled";
        internal const string PROPERTY_MAX_POOL_SIZE = "MaxPoolSize";
        internal const string PROPERTY_MIN_POOL_SIZE = "MinPoolSize";
        internal const string PROPERTY_CREATION_TIMEOUT = "CreationTimeout";
        internal const string PROPERTY_CONSTRUCTION_ENABLED = "ConstructionEnabled";
        internal const string PROPERTY_CONSTRUCTION_STRING = "ConstructorString";
        internal const string PROPERTY_JIT_ACTIVATION = "JustInTimeActivation";
        internal const string PROPERTY_EVENT_TRACKING_ENABLED = "EventTrackingEnabled";
        internal const string PROPERTY_MUST_RUN_IN_CLIENT_CONTEXT = "MustRunInClientContext";
        internal const string PROPERTY_MUST_RUN_IN_DEFAULT_CONTEXT = "MustRunInDefaultContext";
        internal const string PROPERTY_IS_PRIVATE = "IsPrivateComponent";
        internal const string PROPERTY_SYNCHRONIZATION = "Synchronization";
        internal const string PROPERTY_THREADING_MODEL = "ThreadingModel";
        internal const string PROPERTY_EXCEPTION_CLASS = "ExceptionClass";

    #endregion

    #region "CTOR"

        internal COMComponent(COMAdminCatalogObject COMobject, COMAdminCatalogCollection collection, COMApplication parent)
            : base(COMobject, collection)
        {


            _parent = parent;
        }

        protected override void Dispose(bool disposing)
        {

            base.Dispose(disposing);

            if (!_dispose)
            {

                if (_colInterfaces != null)
                {
                    Marshal.ReleaseComObject(_colInterfaces);
                }

                if (_colRoles != null)
                {
                    Marshal.ReleaseComObject(_colRoles);

                }
            }


            _dispose = true;
        }

    #endregion

    #region "Properties"

        public ReadOnlyCollection<COMInterface> Interfaces
        {
            get
            {

                if (_lstInterfaces == null)
                {
                    RetrieveInterfaces();
                }


                return _lstInterfaces;
            }
        }

        public ReadOnlyCollection<string> RolesCoches
        {
            get
            {

                if (_lstRoles == null)
                {
                    RetrieveRoles();
                }


                return _lstRoles;
            }
        }

        public string Name
        {
            get { return base.GetPropertyValueString(PROPERTY_NAME); }
        }

        public Guid ID
        {
            get { return base.GetPropertyValueGUID(PROPERTY_GUID); }
        }

        public string DLLPath
        {
            get { return base.GetPropertyValueString(PROPERTY_DLL); }
        }

        public string Version
        {
            get { return string.Format("{0}.{1}.{2}.{3}", base.GetPropertyValueInteger(PROPERTY_MAJOR_VERSION), base.GetPropertyValueInteger(PROPERTY_MINOR_VERSION), base.GetPropertyValueInteger(PROPERTY_BUILD_VERSION), base.GetPropertyValueInteger(PROPERTY_SUBBUILD_VERSION)); }
        }

        public string Description
        {
            get { return base.GetPropertyValueString(PROPERTY_DESCRIPTION); }
            set { base.SetPropertyValue(PROPERTY_DESCRIPTION, value); }
        }
        public bool ComponentTransactionTimeoutEnabled
        {
            get { return base.GetPropertyValueBoolean(PROPERTY_COMPONENT_TRANSACTION_TIMEOUT_ENABLED); }
            set { base.SetPropertyValue(PROPERTY_COMPONENT_TRANSACTION_TIMEOUT_ENABLED, value); }
        }

        public int ComponentTransactionTimeout
        {
            get { return base.GetPropertyValueInteger(PROPERTY_COMPONENT_TRANSACTION_TIMEOUT); }
            set
            {
                if (value < 0 || value > 3600)
                {
                    throw new OverflowException("La valeur doit être comprise entre 0 et 3600.");
                }
                else
                {
                    base.SetPropertyValue(PROPERTY_COMPONENT_TRANSACTION_TIMEOUT, value);
                }
            }
        }

        public bool AccessChecksEnabled
        {
            get { return base.GetPropertyValueBoolean(PROPERTY_ACCESS_CHECKS_ENABLED); }
            set { base.SetPropertyValue(PROPERTY_ACCESS_CHECKS_ENABLED, value); }
        }

        public bool PoolingEnabled
        {
            get { return base.GetPropertyValueBoolean(PROPERTY_POOLING_ENABLED); }
            set { base.SetPropertyValue(PROPERTY_POOLING_ENABLED, value); }
        }

        public int MaxPoolSize
        {
            get { return base.GetPropertyValueInteger(PROPERTY_MAX_POOL_SIZE); }
            set
            {
                if (value < 1 || value > 1048576)
                {
                    throw new OverflowException("The value must be between 1 and 1048576.");
                }
                else
                {
                    base.SetPropertyValue(PROPERTY_MAX_POOL_SIZE, value);
                }
            }
        }

        public int MinPoolSize
        {
            get { return base.GetPropertyValueInteger(PROPERTY_MIN_POOL_SIZE); }
            set
            {
                if (value < 0 || value > 1048576)
                {
                    throw new OverflowException("The value must be between 0 and 1048576.");
                }
                else
                {
                    base.SetPropertyValue(PROPERTY_MIN_POOL_SIZE, value);
                }
            }
        }

        public int CreationTimeout
        {
            get { return base.GetPropertyValueInteger(PROPERTY_CREATION_TIMEOUT); }
            set
            {
                if (value < 0 || value > 2147483647)
                {
                    throw new OverflowException("The value must be between 0 and 2147483647.");
                }
                else
                {
                    base.SetPropertyValue(PROPERTY_CREATION_TIMEOUT, value);
                }
            }
        }

        public bool ConstructionEnabled
        {
            get { return base.GetPropertyValueBoolean(PROPERTY_CONSTRUCTION_ENABLED); }
            set { base.SetPropertyValue(PROPERTY_CONSTRUCTION_ENABLED, value); }
        }

        public string ConstructionString
        {
            get { return base.GetPropertyValueString(PROPERTY_CONSTRUCTION_STRING); }
            set { base.SetPropertyValue(PROPERTY_CONSTRUCTION_STRING, value); }
        }

        public bool JITActivation
        {
            get { return base.GetPropertyValueBoolean(PROPERTY_JIT_ACTIVATION); }
            set { base.SetPropertyValue(PROPERTY_JIT_ACTIVATION, value); }
        }

        public bool EventTrackingEnabled
        {
            get { return base.GetPropertyValueBoolean(PROPERTY_EVENT_TRACKING_ENABLED); }
            set { base.SetPropertyValue(PROPERTY_EVENT_TRACKING_ENABLED, value); }
        }

        public bool MustRunInClientContext
        {
            get { return base.GetPropertyValueBoolean(PROPERTY_MUST_RUN_IN_CLIENT_CONTEXT); }
            set { base.SetPropertyValue(PROPERTY_MUST_RUN_IN_CLIENT_CONTEXT, value); }
        }

        public bool MustRunInDefaultContext
        {
            get { return base.GetPropertyValueBoolean(PROPERTY_MUST_RUN_IN_DEFAULT_CONTEXT); }
            set { base.SetPropertyValue(PROPERTY_MUST_RUN_IN_DEFAULT_CONTEXT, value); }
        }

        public bool IsPrivate
        {
            get { return base.GetPropertyValueBoolean(PROPERTY_IS_PRIVATE); }
            set { base.SetPropertyValue(PROPERTY_IS_PRIVATE, value); }
        }

    #endregion

    #region "Public methods"

        public void AssociateRole(string name)
        {

            if (_colRoles == null)
            {
                RetrieveRoles();
            }

            if (!_colRoles.AddEnabled)
            {
                throw new NotSupportedException("Associating role is not supported.");
            }

            if (this.Parent.IsSystem)
            {
                throw new NotSupportedException("Cannot associate role in a system application.");
            }

            if (!this.Parent.Changeable)
            {
                throw new NotSupportedException("The application does not support modifications.");
            }

            if (RoleAssociated(name))
            {
                throw new DuplicateNameException(string.Format("The role '{0}' is already associated with this component.", name));
            }

            COMAdminCatalogObject role = (COMAdminCatalogObject)_colRoles.Add();

            role.Value(COMRole.PROPERTY_NAME) = (object)name;

            _colRoles.SaveChanges();


            RetrieveRoles();
        }

        public void DissociateRole(string name)
        {

            if (_colRoles == null)
            {
                RetrieveRoles();
            }

            if (!_colRoles.AddEnabled)
            {
                throw new NotSupportedException("Dissociating role is not supported.");
            }

            if (this.Parent.IsSystem)
            {
                throw new NotSupportedException("Cannot dissociate role in a system application.");
            }

            if (!this.Parent.Changeable)
            {
                throw new NotSupportedException("The application does not support modifications.");
            }

            if (!RoleAssociated(name))
            {
                throw new KeyNotFoundException(string.Format("The role '{0}' is not associated with this component.", name));
            }

            for (int i = 0; i <= _colRoles.Count - 1; i++)
            {
                if (((string)((COMAdminCatalogObject)_colRoles.Item(i)).Name).ToLower() == name.ToLower())
                {
                    _colRoles.Remove(i);
                    break;
                }
            }

            _colRoles.SaveChanges();

            RetrieveRoles();
        }

        public void DissociateRoles()
        {

            for (int i = this.RolesCoches.Count - 1; i >= 0; i += -1)
            {
                DissociateRole(this.RolesCoches(i));

            }
        }

        public bool RoleAssociated(string name)
        {

            bool found = false;

            for (int i = 0; i <= this.RolesCoches.Count - 1; i++)
            {
                if (this.RolesCoches(i).ToLower() == name.ToLower())
                {
                    found = true;
                    break;
                }
            }


            return found;
        }

        public bool InterfaceExists(string name)
        {

            bool found = false;

            for (int i = 0; i <= this.Interfaces.Count - 1; i++)
            {
                if (this.Interfaces(i).Name.ToLower() == name.ToLower())
                {
                    found = true;
                    break;
                }
            }


            return found;
        }

        public COMInterface RetrieveInterface(string name)
        {

            COMInterface interfaceFound = null;

            for (int i = 0; i <= this.Interfaces.Count - 1; i++)
            {
                if (this.Interfaces(i).Name.ToLower() == name.ToLower())
                {
                    interfaceFound = this.Interfaces(i);
                    break;
                }
            }

            if (interfaceFound == null)
            {
                throw new KeyNotFoundException(string.Format("The interface '{0}' was not found.", name));
            }


            return interfaceFound;
        }

        public void Refresh()
        {

            RetrieveInterfaces();

            RetrieveRoles();
        }

        public int CompareTo(object obj)
    {


        return this.Name.CompareTo(((COMComponent)obj).Name);
    }

    #endregion

    #region "Private methods"

        private void RetrieveInterfaces()
        {

            _lstInterfaces = new ReadOnlyCollection<COMInterface>();

            _colInterfaces = base.GetCollection(KEY_COLLECTION_INTERFACE);

            for (int i = 0; i <= _colInterfaces.Count - 1; i++)
            {
                _lstInterfaces.Add(new COMInterface((COMAdminCatalogObject)_colInterfaces.Item(i), _colInterfaces, this));
            }


            _lstInterfaces.Sort();
        }

        private void RetrieveRoles()
    {

        _lstRoles = new ReadOnlyCollection<string>();

        _colRoles = base.GetCollection(KEY_COLLECTION_ROLES);

        for (int i = 0; i <= _colRoles.Count - 1; i++)
        {
            _lstRoles.Add((string)((COMAdminCatalogObject)_colRoles.Item(i)).Name);
        }


        _lstRoles.Sort();
    }

    #endregion
}

Once again, if you want to download the code, you can get it here : ComPlusInterop.zip (31.45 kb).
If you want a complete COM+ programming reference, you can check this MSDN article. This is where I got all the string needed to retrieve property values from COMAdmin.
Enjoy !

Microsoft Dynamics CRM 4.0 tabSet list

As I explained in another article, when you want to retrieve to name of the entity displayed in a CRM page it's easy to check for the etc parameter and then translate the number to a name by calling the MetaData service. But, as I also said, when you check for the page "areas.aspx", you don't have the entity number of the related entity... instead you get a tabSet parameter. The tabSet contains either the relationship name or a tabSet name...

Because the same related entity can have multiple tabSet names, I decided to list everything I found to simplify the job for you (I all got those using Fiddler and clicking on every link of related entities in CRM...).

Area name Related entity name
areaActivities ActivityPointer
areaActivityHistory ActivityPointer
areaAsyncOperations WFProcess
areaRelationships CustomerRelationship
areaRelationship CustomerRelationship
areaContacts Contact
areaSubConts Contact
areaSubAccts Account
areaAddresses CustomerAddress
areaOpps Opportunity
areaOpportunities Opportunity
areaQuotes Quote
areaOrders SalesOrder
areaInvoices Invoice
areaService Incident
areaCases Incident
areaContract Contract
areaListsInSFA List
areaCampaignsInSFA Campaign
areaProducts Product
areaSubs Product
areaComp Competitor
areaComps Competitor
areaSalesLit SalesLiterature
areaItems ProductPriceLevel
areaContractLines ContractDetail
areaRoles Role
areaTeams Team
areaServices Service
areaResourceGroup ResourceGroup
areaResourceGroups ResourceGroup
areaUsers SystemUser
areaBiz BusinessUnit
areaResource Resource
areaUnits UoM
areaPrices ProductPriceLevel
areaMembers SystemUser
areaExistingProducts QuoteDetail
SalesOrder
Invoice
areaWriteInProducts QuoteDetail
SalesOrder
Invoice

All this may save you some hours of clicking !

Posted: Apr 27 2009, 13:46 by WebMasterSam | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: .Net | CRM 4.0 | Programming
Social Bookmarks: E-mail | Kick it! | DZone it! | del.icio.us

Retrieving the entity name or number from a CRM page URL

Recently I had to create an HttpModule that catch every call to a CRM page and inject some JavaScript code in it. The first the HttpModule does is to retrieve to entity name (or number) and instanciates a class that contains some information about the page (organization, page url, entity name, entity number, etc...). Normally it is easy, you can get the etc querystring parameter to get the number and then call the MetaData service to get the entity name. Sometimes the querystring parameter gets another name... it's not always etc: it can be "etc", "oType" or "iObjType".

Life will be too easy if everytime when want to get the entity number we just have to check for the querystring parameter... so Microsoft though about a more challenging way to get the entity number. You will face that if you catch the "areas.aspx" page. This page is the one that is called when you click a left navigation bar item (in an entity record).

Let's check some different URLs you can get in CRM :

For the entity form...

...of CRM entities

 - http://crmserver/org/sfa/accts/edit.aspx (Account)
 - http://crmserver/org/sfa/conts/edit.aspx (Contact)
 - http://crmserver/org/activities/email/edit.aspx (Email activity)

...of user-defined entities

 - http://crmserver/org/userdefined/edit.aspx?etc=#entity_number#

These looks very easy to understand and it's easy to retrieve the entity numer or name. You will have to hard-code the entity number or name in your code when you get CRM entities because the URL (page) tells which entity it is. For the user-defined, you simply get the "etc" parameter in the QueryString.

For the entity record list...

...of the activites, campains and cases

 - http://crmserver/org/workplace/home_activities.aspx
 - http://crmserver/org/ma/home_camps.aspx
 - http://crmserver/org/cs/home_cases.aspx

...of anything else

 - http://crmserver/org/_root/homepage.aspx?etc=#entity_number#

These looks also very easy to understand but you will notice that barely everything (including user-defined entities) are shown in the same page (homepage.aspx) so you will not have to hard-code a lot of thing. And now, for the entity relationship view (the one you get on the right side of the CRM entity form when you click on a left navigation bar item of the entity form)...

...of CRM entities

 - http://crmserver/org/sfa/accts/areas.aspx?oId=[GUID]&oType=1&security=[number]&tabSet=areaContacts (Account showing related contacts)

...of user-defined entities (one-to-many relationship)

 - http://crmserver/org/sfa/accts/areas.aspx?oId=[GUID]&oType=1&security=[number]&tabSet=account_new_myentityname (Account showing related new_myentityname)

...of user-defined entities (many-to-many relationship)

 - http://crmserver/org/sfa/accts/areas.aspx?oId=[GUID]&oType=1&security=[number]&tabSet=areaaccount_new_myentityname (Account showing related new_myentityname)

This not sound very cool... the same querystring parameter (tabSet) can contains 3 different types of value... depending on whether the related entity is custom or built-in and if it is a one-to-many or many-to-many relationship... !

More horrible than this, for the same related entity (shown in different parent entity), the tabSet parameter will not be named the same way (but sometimes it is...). For example, if the related entity is a contact, the tabSet can be named "areaContacts" or "areaSubConts". Wow ! I don't have to tell you it has to be a lot hard-coded !

For the user-defined entities it is a lot more simple than this; the value in the tabSet is the relationship name. This is cool because you can query the CRM MetaData service and get both entities of the relationship. There's only a little twist with this... when you have a many-to-many relationship, the tabSet starts with "area"... I don't know why but MS decided to do it this way so we have to deal with it.

Because the tabSet parameter is a pain, I decided to write an article listing all the different tabSet values I've found in hour of clicking in CRM (because I didn't find it on the web).

Posted: Apr 27 2009, 12:55 by WebMasterSam | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: .Net | CRM 4.0 | Programming
Social Bookmarks: E-mail | Kick it! | DZone it! | del.icio.us
LINK BUILDING IS PROHIBITED ON THIS WEBSITE