rahul's bloghttps://www.rahulsingla.com/blogs/rahul
I always think tomorrow will have more time than today.
And every today seems to pass-by faster than yesterday.enUsing your Gmail account for sending automated SMTP emailshttps://www.rahulsingla.com/blog/2015/10/using-your-gmail-account-for-sending-automated-smtp-emails
<!-- google_ad_section_start --><div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="schema:blogPost content:encoded"><p>Blogging just tad short of a full year, oh well...</p><p>Anyways, I today came back to the admin section of my website after a loooong time and noticed automated email alerts from the site (for example comment notification emails) have been failing for sometime. The site uses a Google Apps account to dispatch emails, and I din't thought anything had changed on the account for smtp email delivery from the site to fail.</p><p>I tried sending a test email and got this error in my site logs (email addresses changed for obvious reasons):</p><blockquote><p>Error sending e-mail from <a href="mailto:test@example.com">test@example.com</a> to <a href="mailto:test2@example.com">test2@example.com</a> : SMTP Error: Could not authenticate.</p></blockquote><p>Hmmmm.. the password hadn't changed though.</p><p>Anyways, I was aware Google had changed/enhanced its security settings over the last year and we had to configure something on a client installation a few months back to enable smtp email delivery from the client's site (the client again using Google Apps). I logged into the Google account used for email delivery and after some playing around, figured I needed to enable the following option to enable smtp email delivery from my site using a gmail account:</p><blockquote><p>"Allow less secure apps"</p></blockquote><p>Here are the steps to precisely reach this setting currently as this blog post is being written (Google tends to regularly update the options available under "Sign-in and Security" section of your Google account):</p><ol><li>Login to your Google Account and click "My Account" from the drop-down on the top-right corner.</li><li>Click "Connected Apps &amp; Sites" under the "Sign-in &amp; Security" section.<br /><br /><a href="/sites/default/files/google-account-settings.jpg" target="_blank" title="Google Account Settings"><img src="/sites/default/files/google-account-settings.jpg" alt="Google Account Settings" title="Google Account Settings" width="150" height="78" /></a></li><li>Finally ensure the "Allow less secure apps" option is set to "ON".<br /><br /> <a href="/sites/default/files/allow-less-secure-apps.jpg" target="_blank" title="Allowing less secure apps"><img src="/sites/default/files/allow-less-secure-apps.jpg" alt="Allowing less secure apps" title="Allowing less secure apps" width="150" height="95" /></a></li></ol><p>That's pretty much it, hopefully this helps some soul.</p><p> </p></div></div></div><!-- google_ad_section_end --><div class="field field-name-taxonomy-vocabulary-13 field-type-taxonomy-term-reference field-label-above"><div class="field-label">Tags:&nbsp;</div><div class="field-items"><div class="field-item even" rel="schema:keywords"><a href="/category/tags/google-0" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Google</a></div><div class="field-item odd" rel="schema:keywords"><a href="/category/tags/smtp" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">SMTP</a></div></div></div><span property="schema:name" content="Using your Gmail account for sending automated SMTP emails" class="rdf-meta element-hidden"></span><span rel="schema:url" resource="/blog/2015/10/using-your-gmail-account-for-sending-automated-smtp-emails" class="rdf-meta element-hidden"></span><ul class="links inline"><li class="addtoany first last"><span><span class="a2a_kit a2a_target addtoany_list" id="da2a_1">
<a class="a2a_dd addtoany_share_save" href="https://www.addtoany.com/share_save#url=https%3A%2F%2Fwww.rahulsingla.com%2Fblog%2F2015%2F10%2Fusing-your-gmail-account-for-sending-automated-smtp-emails&amp;title=Using%20your%20Gmail%20account%20for%20sending%20automated%20SMTP%20emails"><img src="/sites/all/modules/addtoany/images/share_save_171_16.png" width="171" height="16" alt="Share this"/></a>
</span>
<script type="text/javascript">
<!--//--><![CDATA[//><!--
da2a.script_load();
//--><!]]>
</script></span></li>
</ul>Mon, 19 Oct 2015 17:31:49 +0000rahul239 at https://www.rahulsingla.comhttps://www.rahulsingla.com/blog/2015/10/using-your-gmail-account-for-sending-automated-smtp-emails#commentsCrystal Reports - Accessing default value for a parameter using .NET SDKhttps://www.rahulsingla.com/blog/2014/10/crystal-reports-accessing-default-value-for-a-parameter-using-net-sdk
<!-- google_ad_section_start --><div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="schema:blogPost content:encoded"><p>We have been using Crystal Reports for our Travel CRM with good results for a few years now. We use a completely custom ExtJs based experience for Report viewing which includes a custom user interface for entering report parameters (see screenshot below for an example) and a custom toolbar for managing report navigation and exporting etc (I had blogged about the toolbar earlier <a href="http://www.rahulsingla.com/blog/2010/03/an-ajax-toolbar-for-asp-net-crystal-reports">here</a>).</p><p style="text-align: center;"><a href="http://www.rahulsingla.com/sites/default/files/content/blog/custom-ui-for-cr-parameters.png" target="_blank" title="A custom User Interface for entering report parameters for Crystal Reports"><img src="http://www.rahulsingla.com/sites/default/files/content/blog/custom-ui-for-cr-parameters.png" alt="A custom User Interface for entering report parameters for Crystal Reports" title="A custom User Interface for entering report parameters for Crystal Reports" width="500" height="207" /></a></p><p style="text-align: left;"> </p><p style="text-align: left;">Everything worked good. However the client recently came back to us mentioning that our interface does not populate the default value for a report parameter automatically. Well we never supported it earlier, so we began looking for a way to extract the default value for a parameter using the Crystal Reports developer version for Visual Studio SDK to be able to populate the same in our interface. It took us sometime to figure out the way to accomplish this using the SDK, so I thought I would share it for everyone's benefit.</p><p style="text-align: left;">To start with, let's clear out some Crystal Report SDK's nomenclature which is a bit misleading in my opinion. Crystal Report parameters have something known as default values in the SDK. This is not exactly the default value for a parameter, rather a list of options from which you can choose a value (i.e. pre-defined values for a parameter available for selection). For example, in the screenshot below, you are defining available values for the "Detail or Summary" parameter. When executing the report, these values be available in the drop-down as you can see in the screenshot.</p><p style="text-align: center;"><a href="http://www.rahulsingla.com/sites/default/files/content/blog/parameter-default-values.png" target="_blank" title="Parameter Default values in Crystal Reports are actually pre-defined values available for selection"><img src="http://www.rahulsingla.com/sites/default/files/content/blog/parameter-default-values.png" alt="Parameter Default values in Crystal Reports are actually pre-defined values available for selection" title="Parameter Default values in Crystal Reports are actually pre-defined values available for selection" width="500" height="244" /></a></p><p style="text-align: left;"> </p><p style="text-align: left;">Not exactly intuitive you would agree.</p><p style="text-align: left;">Next we come to another option on the parameter which is labeled as "Default value" for the parameter in the Report Designer's user interface (again reference the screenshot below).</p><p style="text-align: center;"><a href="http://www.rahulsingla.com/sites/default/files/content/blog/default-value-for-a-parameter.png" target="_blank" title="Default value for a Crystal Report Parameter"><img src="http://www.rahulsingla.com/sites/default/files/content/blog/default-value-for-a-parameter.png" alt="Default value for a Crystal Report Parameter" title="Default value for a Crystal Report Parameter" width="500" height="439" /></a></p><p style="text-align: left;"> </p><p style="text-align: left;">This is what most people would take as the default value for a parameter in my understanding. The Crystal Reports SDK calls this as "Initial Value" of the parameter; pretty interesting, ain't it...<br /><br /><br />With the background out of our way, let's now come to the code to extract these values from a CR report template using the SDK. I will demonstrate extracting both default as well as initial values of a parameter.</p><p style="text-align: left;">Both these types of values can be extracted from a <em>CrystalDecisions.CrystalReports.Engine.ReportDocument</em> object. Given a path to CR report's template (.rpt) file, here's how you load it into a <em>ReportDocument</em> object:</p><p style="text-align: left;"> </p><p></p><pre class="brush: csharp;fontsize: 100; first-line: 1; ">ReportDocument doc = new ReportDocument();
doc.Load(reportFilePath);</pre><p> </p><p>Next we use the ReportDocument object to extract the default values for each parameter (remember default values are the list of pre-defined values as discussed above).</p><p> </p><p>{syntaxhighlighter brush: csharp;fontsize: 100; first-line: 1; }foreach (ParameterFieldDefinition p in doc.DataDefinition.ParameterFields)
{
if (p.ParameterFieldUsage != ParameterFieldUsage.NotInUse)
{
reportParams.Add(p.Name, new
{
type = p.ParameterValueKind,
label = p.PromptText,
required = (!p.EnableNullValue) || (!p.IsOptionalPrompt),
allowCustom = p.EnableAllowEditingDefaultValue,
allowMultiple = p.EnableAllowMultipleValue,
rangeType = p.DiscreteOrRangeKind,
defaultValues = p.DefaultValues,
initialValues = new
{
discreetValues = new List&lt;object&gt;()
}
});
}
}{/syntaxhighlighter}</p><p> </p><p>We iterate over <em>ReportDocument.DataDefinition.ParameterFields</em> collection. For each parameter, we check if its in use on the report and if yes, extract various configuration options for the parameter including its default values (<em>p.DefaultValues</em>). The above code extract is from our Travel CRM, you can off-course adapt it anyway you need to.</p><p><br />Finally, we extract the <em>InitialValues</em> for each parameter (which you would recall is labeled as default value of the parameter in the Report Designer UI). Again, for some reason, the SDK developers chose to expose the InitialValues using a completely different set of objects (rather than the <em>ParameterFieldDefinition</em>).</p><p> </p><p>{syntaxhighlighter brush: csharp;fontsize: 100; first-line: 1; }foreach (CrystalDecisions.ReportAppServer.DataDefModel.ISCRParameterField clientParameterFieldDefinition in doc.ReportClientDocument.DataDefController.DataDefinition.ParameterFields)
{
foreach (var initialValue in clientParameterFieldDefinition.InitialValues)
{
var discreetInitialValue = initialValue as CrystalDecisions.ReportAppServer.DataDefModel.ParameterFieldDiscreteValue;
if (discreetInitialValue != null)
{
var initialValue = discreetInitialValue.Value;
//Use initial value anyway you would like to. In our case, we stored it and passed to our ExtJs UI to populate in the interface.
}
}
}{/syntaxhighlighter}</p><p> </p><p>You need to goto <em>ReportDocument.ReportClientDocument.DataDefController.DataDefinition.ParameterFields</em> and check the <em>InitialValues</em> collection for each field. Then you ensure each item of the <em>InitialValues</em> collection is of type <em>ParameterFieldDiscreteValue</em>, and if it is, that's your default value for that parameter. It appears the SDK developers chose to keep <em>InitialValues</em> multi-valued so report designers can input multiple default values (aka initial values) for mulit-valued report parameters.</p><p>Please note the last part (extracting <em>InitialValues</em> for a parameter) is written using <em>13.0.10.1385</em> version of the SDK. I believe there's a minor variation to it if you use an earlier version (I don't have the earlier version, so can't tell the difference exactly but you can use intelli-sense or Reflector to find the exact property hierarchy).</p><p>If its different for your SDK version and you aren't able to figure it out, feel free to use the comment form below for seeking help (clearly mention your full SDK version in this case) and I would try to solve the puzzle for you.</p><p> </p></div></div></div><!-- google_ad_section_end --><div class="field field-name-taxonomy-vocabulary-9 field-type-taxonomy-term-reference field-label-above"><div class="field-label">Web 2.0:&nbsp;</div><div class="field-items"><div class="field-item even" rel="schema:category"><a href="/category/web-2-0/extjs" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">ExtJs</a></div></div></div><div class="field field-name-taxonomy-vocabulary-3 field-type-taxonomy-term-reference field-label-above"><div class="field-label">.NET:&nbsp;</div><div class="field-items"><div class="field-item even" rel="schema:category"><a href="/category/net/net-2-0" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">.NET 2.0+</a></div><div class="field-item odd" rel="schema:category"><a href="/category/net/c" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">C#</a></div><div class="field-item even" rel="schema:category"><a href="/category/net/vb-net" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">VB.NET</a></div><div class="field-item odd" rel="schema:category"><a href="/category/net/visual-studio" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Visual Studio</a></div><div class="field-item even" rel="schema:category"><a href="/category/net/crystal-reports" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Crystal Reports</a></div><div class="field-item odd" rel="schema:category"><a href="/category/net/net-4-0" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">.NET 4.0+</a></div><div class="field-item even" rel="schema:category"><a href="/category/net/net-4-0/net-4-5" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">.NET 4.5+</a></div></div></div><span rel="schema:url" resource="/blog/2014/10/crystal-reports-accessing-default-value-for-a-parameter-using-net-sdk" class="rdf-meta element-hidden"></span><span property="schema:name" content="Crystal Reports - Accessing default value for a parameter using .NET SDK" class="rdf-meta element-hidden"></span><ul class="links inline"><li class="addtoany first last"><span><span class="a2a_kit a2a_target addtoany_list" id="da2a_2">
<a class="a2a_dd addtoany_share_save" href="https://www.addtoany.com/share_save#url=https%3A%2F%2Fwww.rahulsingla.com%2Fblog%2F2014%2F10%2Fcrystal-reports-accessing-default-value-for-a-parameter-using-net-sdk&amp;title=Crystal%20Reports%20-%20Accessing%20default%20value%20for%20a%20parameter%20using%20.NET%20SDK"><img src="/sites/all/modules/addtoany/images/share_save_171_16.png" width="171" height="16" alt="Share this"/></a>
</span>
</span></li>
</ul>Sun, 26 Oct 2014 06:28:45 +0000rahul237 at https://www.rahulsingla.comhttps://www.rahulsingla.com/blog/2014/10/crystal-reports-accessing-default-value-for-a-parameter-using-net-sdk#commentsDNN - Implementing IUpgradeable for a module's business controller classhttps://www.rahulsingla.com/blog/2014/10/dnn-implementing-iupgradeable-for-a-modules-business-controller-class
<!-- google_ad_section_start --><div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="schema:blogPost content:encoded"><p>I have to acknowledge, it has probably been more than a couple of years since I have done any real DNN (formerly DotNetNuke) development. Quite a few things seem to have changed in this time, especially with DNN 7.x.</p><p>One of those changes I got stumped on recently was implementing <em>IUpgradeable</em> interface on Imbibe's DNN cache provider module's business controller class. As a DNN developer, you might be aware the <em>IUpgradeable</em> interface implementation allows you to execute custom actions during your module's installation or upgradation process. I needed to add an item to DNN's schedule as well as a custom admin page for the cache provider module in its implementation.</p><p>The last time I implemented <em>IUpgradeable</em> interface should well have been more than a few years ago. That time, it was as simple as implementing the interface on the controller class and specifing the class in the <em>&lt;businessControllerClass&gt;</em> section of the module's manifest file. The same did not work this time unfortunately and no matter what I did, the logic in the <em>UpgradeModule</em> method won't execute on either module installation or upgradation.</p><p>After going back and forth a few times, and opening up DNN's source, it appeared it had something to do with DNN's event processing infrastructure using its <em>EventMessageProcessor</em> class. I then thought of taking a look into how DNN's own built-in modules implemented this interface. After checking one or two modules, it appeared DNN's Taxonomy module implemented this interface. Checking <em>TaxonomyController</em> did not reveal anything interesting, a regular <span style="font-style: italic;">IUpgradeable</span><span style="font-style: italic;"> </span>implementation. Opening up the module's manifest file (<em>Taxonomy.dnn</em>) next and examining it carefully brought up a new aspect, the <em>&lt;eventMessage&gt;</em> section in the manifest file, that looks like this in the <em>Taxonomy</em> module.</p><p> </p><p></p><pre class="brush: xml;fontsize: 100; first-line: 1; ">&lt;eventMessage&gt;
&lt;processorType&gt;DotNetNuke.Entities.Modules.EventMessageProcessor, DotNetNuke&lt;/processorType&gt;
&lt;processorCommand&gt;UpgradeModule&lt;/processorCommand&gt;
&lt;attributes&gt;
&lt;businessControllerClass&gt;DotNetNuke.Modules.Taxonomy.TaxonomyController, DotNetNuke.Modules.Taxonomy&lt;/businessControllerClass&gt;
&lt;desktopModuleID&gt;[DESKTOPMODULEID]&lt;/desktopModuleID&gt;
&lt;upgradeVersionsList&gt;01.00.00,06.00.00&lt;/upgradeVersionsList&gt;
&lt;/attributes&gt;
&lt;/eventMessage&gt;</pre><p>And when I co-related this section with my research into DNN's source code, it started to make sense. Unlike earlier where DNN would just see the interface implementation and execute it, DNN now requires an explicit event message to be present in the module's manifest file providing all the details on what the module is expecting to be done on its behalf by DNN (this information is kept in <em>EventQueue</em> table by DNN).</p><p>Also unlike earlier where DNN executed the <em>UpgradeModule</em> implementation for each data provider file version (e.g. 01.00.00.SqlDataProvider), it now needs the module to clearly state the versions list for which <em>UpgradeModule</em> should be called in the <em>&lt;upgradeVersionsList&gt;</em> element.</p><p>Thinking about these changes later made sense. Module developers earlier often needed to provide empty DataProvider files for specific versions just so DNN would call <em>UpgradeModule</em> for those versions when the module is upgraded for an installation. Allowing to specify it explicitly instead provides more control and flexibility to module developers.</p><p>So in my case, I had to add this <em>&lt;eventMessage&gt;</em> in the manifest file to get DNN to invoke <em>UpgradeModule</em> for us.</p><p> </p><p></p><pre class="brush: xml;fontsize: 100; first-line: 1; ">&lt;eventMessage&gt;
&lt;processorType&gt;DotNetNuke.Entities.Modules.EventMessageProcessor, DotNetNuke&lt;/processorType&gt;
&lt;processorCommand&gt;UpgradeModule&lt;/processorCommand&gt;
&lt;attributes&gt;
&lt;businessControllerClass&gt;Imbibe.Dnn.CachingProvider.OutputCache.TabCacheController, Imbibe.Dnn.CachingProvider&lt;/businessControllerClass&gt;
&lt;desktopModuleID&gt;[DESKTOPMODULEID]&lt;/desktopModuleID&gt;
&lt;upgradeVersionsList&gt;01.00.00&lt;/upgradeVersionsList&gt;
&lt;/attributes&gt;
&lt;/eventMessage&gt;</pre><p>Please note the <em>&lt;eventMessage&gt;</em> element goes as a direct child of the <em>&lt;component&gt;</em> element (and not inside <em>&lt;desktopModule&gt;</em> or <em>&lt;moduleDefinition&gt;</em> elements). <em>[DESKTOPMODULEID]</em> acts as a placeholder that DNN replaces dynamically for your module. The <em>&lt;upgradeVersionsList&gt;</em> is a comma-separated list of versions and DNN invokes <em>UpgradeModule</em> for each version in this list. The usual rules apply (i.e. <em>UpgradeModule</em> is only called for versions listed which are later than the currently installed version and earlier than the <em>version</em> listed in the <em>package</em> element; which means it won't be invoked at all if you just re-install an already installed version).</p><p>This is my guess but I think these changes are related to the manifest file's version too. I was using 5.0 version manifest. Not sure if DNN 7.x still supports it, but 3.0 version manifests did not need an explicit <em>&lt;eventMessage&gt;</em> to be registered for <em>IUpgradeable</em> implementations.</p><p>You can't really leave technology for long, it changes sooner than you get a hang of it these days <img src="http://www.rahulsingla.com/sites/all/libraries/tinymce/jscripts/tiny_mce/plugins/emotions/img/smiley-smile.gif" alt="Smile" title="Smile" border="0" /></p><p> </p></div></div></div><!-- google_ad_section_end --><div class="field field-name-taxonomy-vocabulary-7 field-type-taxonomy-term-reference field-label-above"><div class="field-label">CMSes:&nbsp;</div><div class="field-items"><div class="field-item even" rel="schema:category"><a href="/category/cmses/dotnetnuke" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">DotNetNuke</a></div><div class="field-item odd" rel="schema:category"><a href="/category/cmses/dotnetnuke/dotnetnuke-5-x" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">DotNetNuke 5.x</a></div><div class="field-item even" rel="schema:category"><a href="/category/cmses/dotnetnuke/dotnetnuke-6-x" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">DotNetNuke 6.x</a></div><div class="field-item odd" rel="schema:category"><a href="/category/cmses/dotnetnuke/dotnetnuke-7-x" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">DotNetNuke 7.x</a></div><div class="field-item even" rel="schema:category"><a href="/category/cmses/dotnetnuke/dnn-5-x" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">DNN 5.x</a></div><div class="field-item odd" rel="schema:category"><a href="/category/cmses/dotnetnuke/dnn-6-x" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">DNN 6.x</a></div><div class="field-item even" rel="schema:category"><a href="/category/cmses/dotnetnuke/dnn-7-x" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">DNN 7.x</a></div></div></div><div class="field field-name-taxonomy-vocabulary-3 field-type-taxonomy-term-reference field-label-above"><div class="field-label">.NET:&nbsp;</div><div class="field-items"><div class="field-item even" rel="schema:category"><a href="/category/net/asp-net" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">ASP.NET</a></div><div class="field-item odd" rel="schema:category"><a href="/category/net/net-2-0" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">.NET 2.0+</a></div><div class="field-item even" rel="schema:category"><a href="/category/net/c" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">C#</a></div><div class="field-item odd" rel="schema:category"><a href="/category/net/visual-studio" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Visual Studio</a></div><div class="field-item even" rel="schema:category"><a href="/category/net/net-4-0" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">.NET 4.0+</a></div><div class="field-item odd" rel="schema:category"><a href="/category/net/net-4-0/net-4-5" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">.NET 4.5+</a></div><div class="field-item even" rel="schema:category"><a href="/category/net/asp-net/asp-net-4-0" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">ASP.NET 4.0</a></div></div></div><span rel="schema:url" resource="/blog/2014/10/dnn-implementing-iupgradeable-for-a-modules-business-controller-class" class="rdf-meta element-hidden"></span><span property="schema:name" content="DNN - Implementing IUpgradeable for a module&#039;s business controller class" class="rdf-meta element-hidden"></span><ul class="links inline"><li class="addtoany first last"><span><span class="a2a_kit a2a_target addtoany_list" id="da2a_3">
<a class="a2a_dd addtoany_share_save" href="https://www.addtoany.com/share_save#url=https%3A%2F%2Fwww.rahulsingla.com%2Fblog%2F2014%2F10%2Fdnn-implementing-iupgradeable-for-a-modules-business-controller-class&amp;title=DNN%20-%20Implementing%20IUpgradeable%20for%20a%20module%27s%20business%20controller%20class"><img src="/sites/all/modules/addtoany/images/share_save_171_16.png" width="171" height="16" alt="Share this"/></a>
</span>
</span></li>
</ul>Sat, 25 Oct 2014 06:21:07 +0000rahul236 at https://www.rahulsingla.comhttps://www.rahulsingla.com/blog/2014/10/dnn-implementing-iupgradeable-for-a-modules-business-controller-class#commentsA PowerShell based FTP runner for TeamCity buildshttps://www.rahulsingla.com/blog/2014/09/a-powershell-based-ftp-runner-for-teamcity-builds
<!-- google_ad_section_start --><div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="schema:blogPost content:encoded"><p>This blog post can be seen in continuation of my last post proposing <a href="http://www.rahulsingla.com/blog/2014/09/a-deployment-strategy-for-drupal-sites">a deployment strategy for Drupal installations</a>, or an independent post in itself. The objective is to create a Build Runner for TeamCity builds that can upload/push (all or selectively) files/folders affected in each build to one or more remote servers over FTP for deployment.</p><p>The intent initially was indeed to automate deployment for Drupal installations but the (Powershell) scripts are written to provide a completely autonomous mechanism to trigger FTP uploads to remote servers for files/folders affected in each TeamCity build. The end-result is a set of PowerShell scripts that can be configured as a build step to achieve the stated objective.</p><p>To provide a bit of a background on why PowerShell was chosen, I originally intended to write a native TeamCity build runner taking cues from the code of existing runners open-sourced by JetBrains. However, I did not had a TeamCity dev enviornment available at hand for native Java-based TeamCity development. The fact that .Net provides a very friendly <a href="http://msdn.microsoft.com/en-us/library/system.net.ftpwebrequest(v=vs.110).aspx" target="_blank">FtpWebRequest</a> class that makes creating a custom FTP client a breeze, turned the tide in favour of a .Net based solution. And finally our TeamCity servers being all Windows based, sealed the decision in favor of PowerShell (further aided by the availability of a very powerful PwerShell build runner in TeamCity out-of-the-box).</p><p>The title of the post can technically be considered a bit inaccurate as its now amply clear we are not creating a new build runner in its purest sense but a set of scripts riding on the PowerShell build runner. But I would still like to perceive it as a FTP runner for TeamCity riding on the PowerShell build runner :)</p><p>Okay so now, these were the design objectives that I thought the FTP runner should satisfy:</p><ol><li>Deep integration with TeamCity utilizing all customizability TeamCity has to offer (including elaborate build FTP messages for each build).</li><li>Build-oriented: a TeamCity build is usually triggered by commits to a version control system. The TeamCity admin can decide if each commit should trigger a new build or specify a set of conditions to combine multiple commits into a single build. The runner should integrate with this TeamCity's behavior and should be able to cope with single, multiple or even no commits in a build or when builds are triggered manually.</li><li>The runner should only push the files/folders affected in a build to the remote server (and not the entire content under source control).</li><li>If multiple commits are combined together in a single build and a file changed multiple times in those commits, only a single upload for that file should be trigerred (for the last version of that file included in the build).</li><li>Some source control systems do not track directories explicitly (e.g. Git) in the metadata. They only track files and directories are assumed to be present implicitly for the paths where files exist (and the SCM client is expected to automatically create the parent directory for each path where a file exists).<br />This means no directory information would be recorded in TeamCity or SCM logs when files are added/removed. The runner should be able to atleast automatically create directories for paths where files are added/modified on the remote server if they do not already exist.</li><li>The FTP parameters (remote FTP url, ftp credentials etc) should not be hard-coded in the scripts and configurable via TeamCity's interface.</li><li>Ability to selectively push sub-folders from the content under version control to the remote server (and not necessarily track the entire source for pushing out remotely).</li><li>Ability to define mappings for source control folders and remote FTP folders. So for examples you should be able to send content from <em>/folder1</em> in the content under source-control to <em>folder2/sub-folder</em> on the remote server.</li><li>For specific builds, ability to temporarily suspend FTP upload and not perform any action for those builds at all.</li></ol><p>I would like to mention here that I found a third-party FTP runner available for TeamCity, and I played with it too. However it did not satisfy many of the listed objectives and the single most important reason for rejecting it out-right was it was trying to re-upload the entire content under source control to the remote server for each TeamCity build. It might be okay doing this for specific use-cases but isn't acceptable generally I would say; and wasn't at all for us considering our use-case of <a href="http://www.rahulsingla.com/blog/2014/09/a-deployment-strategy-for-drupal-sites">automating Drupal deployments</a>.</p><p>Now some insights into TeamCity's behaviour and internals much of which I discovered while writing these scripts for FTP deployment (and each successive discovery increased the wow factor for TeamCity, considering how carefully its thought out for extensibility. DISCLAIMER: this is not sponsored content <img src="http://www.rahulsingla.com/sites/all/libraries/tinymce/jscripts/tiny_mce/plugins/emotions/img/smiley-laughing.gif" alt="Laughing" title="Laughing" border="0" />, but my own assessment after what I worked with and found).</p><ol><li>TeamCity provides three types of parameters for each build, <em>Configuration parameters</em>, <em>System properties</em> and <em>Environment variables</em>. There are subtle but important aspects of the scope and existence perimeter of each type of variables which usually would affect which type to opt when defining a custom parameter. More details on these parameter types <a href="http://confluence.jetbrains.com/display/TCD8/Configuring+Build+Parameters" target="_blank">here</a>.</li><li>TeamCity maintains a list of all files/directories affected by each build in a text file whose path is accessible via system property <em>teamcity.build.changedFiles.file</em>.</li><li>This file only exists during the life-time of a build and is removed once a build completes.</li><li>The file contains a single line for each file/folder affected in any of the commits in the build.<ol><li>A file/folder affected by multiple commits in a single build is still listed only once.</li><li>Whether folders are listed depends upon whether your SCM tracks folders explicitly.</li><li>Each line contains 3 parts separated by colon (:) in this order:<ol><li>File/folder path relative to repo root.</li><li>Type of change for a file/folder(more details <a href="http://confluence.jetbrains.com/display/TCD8/Risk+Tests+Reordering+in+Custom+Test+Runner" target="_blank">here</a>).</li><li>Last Revision identifier for the file/folder in the build (revision identifier for example would be Revision number in SVN and commit hash in Git).</li></ol></li><li>The file would still exist if a build contains no SCM revisions, but would be empty in that case.</li></ol>I have attached a sample such file with this blog post (<em>changedFiles7913905745611206951.txt</em>).</li><li>TeamCity provides all system properties (and some other build parameters) available as a .ini formatted file whose path is provided as a system property, <em>teamcity.build.properties.file</em> as well as via environment variable, <em>TEAMCITY_BUILD_PROPERTIES_FILE</em>. Although undocumented, the same set of properties is also made available as a xml file whose path can be calculated by suffixing ".xml" extension to the value returned by <em>teamcity.build.properties.file</em> system property or <em>TEAMCITY_BUILD_PROPERTIES_FILE</em> environment variable. Again both these files only exist for the duration of a build.<br /><br />Any custom system properties defined are included in both these files and thus can be accessed in build scripts. I have attached both sample ini and xml build properties files below. These also contain custom parameters that are required by the powershell FTP upload scripts.</li><li>TeamCity provides a very exhaustive support for build scripts to write to TeamCity's build log, more details available <a href="http://confluence.jetbrains.com/display/TCD8/Build+Script+Interaction+with+TeamCity#BuildScriptInteractionwithTeamCity-ServiceMessages" target="_blank">here</a>.</li></ol><p>This was enough enlightment about TeamCity support needed to write our FTP upload PS scripts that would achieve the stated objectives. However I would like to describe the parameters these scripts need before discussing the scripts themselves. All in all, the scripts utilize 3 required and 2 optional parameters:</p><ol><li><em>ftpBaseUrl</em><em> (required)</em>: The base url relative to which affected files/folders are pushed. This could be a server root (e.g. <em><a href="ftp://example.com">ftp://example.com</a></em>) or a sub-directory (e.g. <em><a href="ftp://example.com/somefolder/">ftp://example.com/somefolder/</a></em>).</li><li><em>ftpUsername</em><em> </em><em>(required)</em>: The username for the remote FTP server.</li><li><em>ftpPassword</em><em> (required)</em>: The password for the remote FTP server.</li><li><em>ftpPathMappings</em><em> (optional)</em>: This parameter allows you to re-map files/folders from your repo source to different files/folders on the remote server; or selectively push specific files/folders only to the remote server. You can enter multiple re-mapping rules semi-colon separated.<br /><br />Please note all paths should be relative to your repo source on one side and <em>ftpBaseUrl</em> parameter value on the other side. I would take a few examples to demonstrate the flexibility provided by this configuration parameter.<ol><li>In its simplest form, you can omit specifying any value for this parameter. In this case, all files/folders affected in a build from source repo would be pushed to remote server in the same hierarchy. The scripts would automatically take care of creating the folders as needed if they don't exist.</li><li><em>/folder1=&gt;/folder2</em><br />The above rule would cause mapping of <em>folder1</em> in source repo to <em>folder2</em> on remote ftp server, thus meaning any content from under <em>folder1</em> in source repo goes to <em>folder2</em> on ftp server.<br /><br />Please also note if this parameter value is non-empty, only those paths in the source repo affected by a build are pushed to the ftp server that satisfy one of the re-mapping rules specified by this property value. All other paths affected by the build are ignored. Therefore in this case, if <em>/web/readme.txt</em> file is affected by a build, it won't be pushed to the remote server.</li><li><em>/folder1=&gt;/folder2;folder3=&gt;folder4/sub-folder</em><br />Content affected by the build under <em>folder1</em> in source repo goes to <em>folder2</em> on remote server and from <em>folder3</em> in source repo goes to <em>folder4/sub-folder</em> on remote server. All other affected paths not satisfying any of these re-mapping rules are ignored.<em> </em></li><li><em>/folder1=&gt;/folder2;folder3=&gt;folder4/sub-folder;/=&gt;/</em><br />The first two re-mapping rules work exactly the same is described in previous example. The interesting thing is all other affected paths not satisfying the first 2 re-mapping rules are pushed in their source hierarchy as is to the remote server because of the third re-mapping rule <em>/=&gt;/</em>, which affectively says upload all other affected paths not matching any of the previous re-mapping rules as is to the remote server.<br /><br />Please note if you are selectively re-mapping content and pushing the rest of the content as is, then the "match-all" re-mapping rule (i.e. <em>/=&gt;/)</em> should be the last rule in the semi-colon separated list. This also means more specific rules (e.g. <em>folder1/sub-folder=&gt;folder2</em>) should occur before less specific rules (e.g. <em>folder1=&gt;folder3</em>) if they have a common source path prefix (<em>folder1</em> in the last example).<em> </em></li></ol></li><li><em>ftpSuspendUploads (optional):</em> This is a boolean (checkbox type) parameter which when set causes the scripts to not process FTP uploads for affected paths for builds where this parameter is set to true. Please note this parameter's type should be set to <em>checkbox</em> with <em>true</em> as its <em>Checked value</em> and anything for <em>Unchecked value</em>.</li></ol><p>Because of the way TeamCity parameters work, you should specify all these 5 parameters as <em>System properties</em> on your build. Starting with TeamCity 8.1, you are able to edit Spec for a parameter and select <em>Password</em> for a parameter's type which masks the parameter's value all over the TeamCity's interface but making it available to build scripts in the properties files. <a href="http://www.rahulsingla.com/sites/default/files/content/blog/teamcity-password-spec-for-parameters.png" target="_blank"><img src="http://www.rahulsingla.com/sites/default/files/content/blog/teamcity-password-spec-for-parameters.png" alt="Teamcity password-spec-for a parameter" title="Teamcity password-spec-for a parameter" width="100" height="69" style="float: right;" /></a>You would want to specify type <em>Password</em> for both <em>ftpUsername</em> and <em>ftpPassword</em> parameters and possibly for <em>ftpBaseUrl</em> too. The screenshot to the right demonstrates how you can edit <em>Spec</em> for a parameter and choose type as password (please click on the screenshot to enlarge).</p><p> </p><p>I would now jump to the scripts and give a brief overview of each of the files that comprise the set of scripts performing the actual FTP upload functionality (attached with this blog post as <em>TeamCity.FtpRunner.zip</em>). Please note only <em>TeamCity.FtpRunner.zip</em> contains the actual scripts, all other attachments are sample TeamCity files to help understand the working of the scripts and need not be deployed.</p><ol><li>The first file you should take a look at from the .zip package is <em>FtpHelper.cs</em>. It is simply a wrapper for .Net's FtpWebRequest class making it easier to invoke all FTP functionality from PowerShell in a single line taking care of plumbing for creating FTP requests and parsing FTP reponses.<br />Despite all the flexibility of PS, I felt is was still easier to write a non-trivial class in C#. PowerShell's <em><a href="http://technet.microsoft.com/en-us/library/hh849914.aspx" target="_blank">Add-Type</a></em> support made consuming the wrapper class in PS a breeze.</li><li><em>TeamCity.FtpHelper.ps1</em>: This is a helper script making it easier to intergrate with TeamCity. Currently it provides utility functions making it easier to write to TeamCity's build log.</li><li><em>TeamCity.FtpRunner.ps1</em>: This is the main script that does all the heavy-lifting. This is what you would point to when creating the PowerShell build step in TeamCity. The script implements all the logic for TeamCity integration and triggering FTP functionality, the background of which has been provided above.<br />You don't really need to study the scripts if all your are interested in is just configuring a build for FTP uploads. For the nerds, feel free to open and dive into the actual logic.</li><li><em>License.txt</em>: The scripts' use is governed by Microsoft's Public License and the file contains the text of the license.</li></ol><p>The below screenshot demonstrates sample configuration for a PowerShell build step to utilize these scripts (please click to enlarge the screenshot):</p><p style="text-align: center;"><a href="http://www.rahulsingla.com/sites/default/files/content/blog/ftp-deployment-ps-build-step.png" target="_blank"><img src="http://www.rahulsingla.com/sites/default/files/content/blog/ftp-deployment-ps-build-step.png" alt="Ftp Deployment PowerShell Build Step" title="Ftp Deployment PowerShell Build Step" width="200" height="120" /></a></p><p>Now comes the question, how would you get the scripts on your build server. There are a couple of ways of doing that:</p><ol><li>You can either manually copy them to the build server at a known location and then configure the build step pointing the script path to that location.</li><li>Or you can put the scripts into your repo itself in some location that is not pushed to the remote ftp server. This usually means you will have to specify <em>ftpPathMappings</em> to exclude the scripts themselves from being uploaded to the remote server (unless you want these scripts also to be uploaded). If you do not want to use <em>ftpPathMappings</em>, then the more appropriate way is to copy them to the build server manually at a known location.</li></ol><p>I have provided detailed steps to create a <em><a href="http://www.rahulsingla.com/blog/2014/09/a-deployment-strategy-for-drupal-sites#configuration-template" target="_blank">Configuration Template</a></em> for these FTP runner scripts and subsequently creating a <a href="http://www.rahulsingla.com/blog/2014/09/a-deployment-strategy-for-drupal-sites#build-configuration" target="_blank">Build Configuration based on the template</a> in my other blog post that I do not feel like repeating all over here (so please follow the links for getting assistance with creating the configuration template and build configuration to actually utilize these scripts).</p><p>The scripts are also available as a GitHub repo at the following url in case you are interested in checking-out the commit log or contributing to the scripts:<br /><a href="https://github.com/imbibe/TeamCityPsFtpRunner" target="_blank">https://github.com/imbibe/TeamCityPsFtpRunner</a></p><p>As always, please feel free to use the comment section to let me know what you think about the approach and if you need help with anything related to this blog post.</p><p> </p></div></div></div><!-- google_ad_section_end --><div class="field field-name-taxonomy-vocabulary-11 field-type-taxonomy-term-reference field-label-above"><div class="field-label">System:&nbsp;</div><div class="field-items"><div class="field-item even" rel="schema:category"><a href="/category/system/windows/powershell" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">PowerShell</a></div><div class="field-item odd" rel="schema:category"><a href="/category/system/windows/powershell/powershell-2" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">PowerShell 2</a></div></div></div><div class="field field-name-taxonomy-vocabulary-13 field-type-taxonomy-term-reference field-label-above"><div class="field-label">Tags:&nbsp;</div><div class="field-items"><div class="field-item even" rel="schema:keywords"><a href="/taxonomy/term/297" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">teamcity ftp</a></div><div class="field-item odd" rel="schema:keywords"><a href="/taxonomy/term/298" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">powershell ftp</a></div><div class="field-item even" rel="schema:keywords"><a href="/taxonomy/term/299" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">teamcity powershell example</a></div><div class="field-item odd" rel="schema:keywords"><a href="/taxonomy/term/300" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">powershell ftpwebrequest</a></div><div class="field-item even" rel="schema:keywords"><a href="/taxonomy/term/301" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">teamcity password parameter</a></div><div class="field-item odd" rel="schema:keywords"><a href="/taxonomy/term/302" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">teamcity deploy powershell</a></div><div class="field-item even" rel="schema:keywords"><a href="/taxonomy/term/303" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">powershell ftp upload</a></div><div class="field-item odd" rel="schema:keywords"><a href="/taxonomy/term/304" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">powershell ftp upload script</a></div><div class="field-item even" rel="schema:keywords"><a href="/taxonomy/term/305" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">teamcity set environment variable in build step</a></div><div class="field-item odd" rel="schema:keywords"><a href="/taxonomy/term/306" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">ftp powershell</a></div><div class="field-item even" rel="schema:keywords"><a href="/taxonomy/term/307" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">powershell ftp script</a></div><div class="field-item odd" rel="schema:keywords"><a href="/taxonomy/term/308" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">powershell ftp module</a></div><div class="field-item even" rel="schema:keywords"><a href="/taxonomy/term/309" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">teamcity messages folder</a></div><div class="field-item odd" rel="schema:keywords"><a href="/taxonomy/term/310" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">powershell ftp connection</a></div><div class="field-item even" rel="schema:keywords"><a href="/taxonomy/term/311" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">powershell upload to ftp</a></div><div class="field-item odd" rel="schema:keywords"><a href="/taxonomy/term/312" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">powershell ftp client</a></div><div class="field-item even" rel="schema:keywords"><a href="/taxonomy/term/313" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">powershell ftp upload multiple files</a></div><div class="field-item odd" rel="schema:keywords"><a href="/taxonomy/term/314" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">teamcity wikipedia</a></div><div class="field-item even" rel="schema:keywords"><a href="/taxonomy/term/315" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">powershell upload file to ftp</a></div><div class="field-item odd" rel="schema:keywords"><a href="/taxonomy/term/316" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">powershell upload ftp</a></div></div></div><div class="field field-name-taxonomy-vocabulary-12 field-type-taxonomy-term-reference field-label-above"><div class="field-label">Enterprise Servers:&nbsp;</div><div class="field-items"><div class="field-item even" rel="schema:category"><a href="/category/enterprise-servers/teamcity" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">TeamCity</a></div><div class="field-item odd" rel="schema:category"><a href="/category/enterprise-servers/teamcity/teamcity-8-x" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">TeamCity 8.x</a></div></div></div><div class="field field-name-taxonomy-vocabulary-3 field-type-taxonomy-term-reference field-label-above"><div class="field-label">.NET:&nbsp;</div><div class="field-items"><div class="field-item even" rel="schema:category"><a href="/category/net/net-2-0" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">.NET 2.0+</a></div><div class="field-item odd" rel="schema:category"><a href="/category/net/net-4-0" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">.NET 4.0+</a></div><div class="field-item even" rel="schema:category"><a href="/category/net/net-4-0/net-4-5" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">.NET 4.5+</a></div><div class="field-item odd" rel="schema:category"><a href="/category/net/c" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">C#</a></div></div></div><div class="field field-name-upload field-type-file field-label-hidden"><div class="field-items"><div class="field-item even"><table class="sticky-enabled">
<thead><tr><th>Attachment</th><th>Size</th> </tr></thead>
<tbody>
<tr class="odd"><td><span class="file"><img class="file-icon" alt="Package icon" title="application/zip" src="/modules/file/icons/package-x-generic.png" /> <a href="https://www.rahulsingla.com/sites/default/files/content/blog/TeamCity.FtpRunner.zip" type="application/zip; length=7051" title="TeamCity.FtpRunner.zip">TeamCity.FtpRunner.zip</a></span></td><td>6.89 KB</td> </tr>
<tr class="even"><td><span class="file"><img class="file-icon" alt="Plain text icon" title="text/plain" src="/modules/file/icons/text-plain.png" /> <a href="https://www.rahulsingla.com/sites/default/files/content/blog/changedFiles7913905745611206951.txt" type="text/plain; length=136" title="changedFiles7913905745611206951.txt">changedFiles7913905745611206951.txt</a></span></td><td>136 bytes</td> </tr>
<tr class="odd"><td><span class="file"><img class="file-icon" alt="Binary Data" title="application/octet-stream" src="/modules/file/icons/application-octet-stream.png" /> <a href="https://www.rahulsingla.com/sites/default/files/content/blog/teamcity.build6315436736667942286.properties" type="application/octet-stream; length=2675" title="teamcity.build6315436736667942286.properties">teamcity.build6315436736667942286.properties</a></span></td><td>2.61 KB</td> </tr>
<tr class="even"><td><span class="file"><img class="file-icon" alt="File" title="application/xml" src="/modules/file/icons/application-octet-stream.png" /> <a href="https://www.rahulsingla.com/sites/default/files/content/blog/teamcity.build6315436736667942286.properties.xml" type="application/xml; length=3519" title="teamcity.build6315436736667942286.properties.xml">teamcity.build6315436736667942286.properties.xml</a></span></td><td>3.44 KB</td> </tr>
</tbody>
</table>
</div></div></div><span property="schema:name" content="A PowerShell based FTP runner for TeamCity builds" class="rdf-meta element-hidden"></span><span rel="schema:url" resource="/blog/2014/09/a-powershell-based-ftp-runner-for-teamcity-builds" class="rdf-meta element-hidden"></span><ul class="links inline"><li class="addtoany first last"><span><span class="a2a_kit a2a_target addtoany_list" id="da2a_4">
<a class="a2a_dd addtoany_share_save" href="https://www.addtoany.com/share_save#url=https%3A%2F%2Fwww.rahulsingla.com%2Fblog%2F2014%2F09%2Fa-powershell-based-ftp-runner-for-teamcity-builds&amp;title=A%20PowerShell%20based%20FTP%20runner%20for%20TeamCity%20builds"><img src="/sites/all/modules/addtoany/images/share_save_171_16.png" width="171" height="16" alt="Share this"/></a>
</span>
</span></li>
</ul>Mon, 15 Sep 2014 08:14:02 +0000rahul234 at https://www.rahulsingla.comhttps://www.rahulsingla.com/blog/2014/09/a-powershell-based-ftp-runner-for-teamcity-builds#commentsA deployment strategy for Drupal siteshttps://www.rahulsingla.com/blog/2014/09/a-deployment-strategy-for-drupal-sites
<!-- google_ad_section_start --><div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="schema:blogPost content:encoded"><p>Oh well, what do you call a system log that has no entries; a malfunctioning system, right. So what would you call a supposedly web log (blog) that has no entries for almost a year and a handful in a couple years <img src="http://www.rahulsingla.com/sites/all/libraries/tinymce/jscripts/tiny_mce/plugins/emotions/img/smiley-laughing.gif" alt="Laughing" title="Laughing" border="0" />?</p><p>Despite deeply wanting to keep writing, I hardly get a chance to do so these days. But so much for the ranting, time to get down to business now.</p><p><a href="mailto:We@Imbibe">We@Imbibe</a> do a lot of Drupal stuff (in-house as well as consulting). This site itself, <a href="http://imbibe.in">Imbibe's site</a>, <a href="http://careers.imbibe.in">Imbibe's careers site</a> and many of our other web properties are Drupal based; so are many of our client's properties, e.g. <a href="http://www.sfequipments.com/" target="_blank">this</a>, <a href="http://arpanaservices.org/" target="_blank">this</a> and <a href="http://tcmengineering.com" target="_blank">this</a> etc.</p><p>The development and maintenance of all these web properties, as should be obvious, involves doing stuff locally that then gets pushed out to production as well as fetching content from production to do more stuff locally (e.g. coding a client requested change to a workflow locally and pushing to production upon approval; or fetching backups of client-entered content from production so they can be customized as requested).</p><p>In almost all cases, the production environment is a highly sensitive set of servers with regulated access. And there's bureaucracy involved in pushing content to production or fetching from it (which I agree is justified).</p><p>For sometime now, I had been looking to automate the process of sending content back and forth from and to production for these web properties in a way that required minimal manual intervention (especially minimizing or eliminating the process of requesting access to the production servers) but enough administrative oversight. At a minimum, the desirable objectives of a deployment strategy for these properties included the following:</p><ol><li>No need for requesting manual Shell, Hosting Panel or FTP access for production servers either for retrieving data from them or for pushing out data to them.</li><li>Ability for our design or development team to request database backups and/or file-system backups of content uploaded to production.<ol><li>If a client authorized our in-house administrator to fetch these backups, the admin should be able to get those from production and provide to the team.</li><li>Else an admin or manager at the client should be able to send us these backups.<br /><br />In both cases, the backups should be retrievable via a web-interface satisfying condition 1 above (no manual access required to the server).</li></ol></li><li>The changes produced by design or development team should be sent for staging and approval. Once approved, either our in-house admin or the client admin should be able to trigger automatic upload of the changes to production. The upload should only send modified files to the production (and not re-upload the entire site).</li><li>We should be able to upgrade off-the shelf modules and/or themes deployed for a Drupal install via the same deployment process. If needed, Drupal itself should be upgradable by utilizing the same deployment process.</li><li> The entire process should be as less painful as possible with a possibility of manual oversight and approval involved wherever needed.</li></ol><p><br />I would now like to switch to providing some background on Drupal that we considered while devising a way to deployment automation (these points are true for single or multi-site installations, however there are a few optimizations that can be made further for multi-site Drupal installs):</p><ol><li>A Drupal installation is supported by a single database (and it was MySql for all our installations).</li><li>Drupal maintains a very clear separation between the CMS portion of a deployment and the content portion of a deployment:<ol><li>Its easy to version control the CMS portion of the deployment by using a SCM tool. We use Git but the strategy we devised isn't tied to a particular SCM.</li><li>The content part could mean 2 things:<ol><li>The inputted content which would normally be in a database.</li><li>The uploaded content which would be in public or private folders of a Drupal site.</li></ol></li><li>For the uploaded content, you don't normally need to put it under source control (in fact, you would never want to put it under source control).</li><li>The dev/design teams usually do not need latest uploaded content from public or private folders of a Drupal installation all the time, but might need it occasionally and should be able to request it then.</li><li>They again might not need latest content from the live database but should be able to request it if needed.</li></ol></li></ol><p>I won't goto the brainstorming and deliberation phase which helped produce the deployment strategy I am going to describe shortly. I would rather list the high-level points of the strategy (which seems to have started working pretty well) and helped achieve the desirable objectives outlined above. You should re-read the Drupal background provided as that's an important aspect of the entire discussion.</p><p>Now for the strategy:</p><ol><li>As a rule of thumb, we always put the CMS portion of the sites we manage (for ourselves or our clients) under source control.<br />As mentioned, we use Git for SCM (but you can use any). A couple of sites which weren't under source control had repos created for them for automating deployment.<br /><br />Please note we only added Drupal or contrib portions under source control. Uploaded content (which usually includes images or auto-generated content by Drupal like minified css/js files) weren't put under source control. Folders containing these files (which is usually the public files folder in Drupal) was actually added to .gitignore (i.e. ignore list in source control).</li><li>All these sites are a consulting or maintenance job. So every site gets its source control repo which are totally independent.</li><li>We create 2 branches for each repo, <em>master</em> and <em>prod</em>.<ol><li><em>master</em> is where the initial content gets checked into with the production backup with site at a stable state.</li><li><em>prod</em> branch is immediately created from <em>master</em>.</li><li>All further dev and customization work would happen in <em>master</em>. When changes get stable and approved, the commits are merged to the <em>prod</em> branch.</li></ol></li><li>We decided to use JetBrains' <a href="http://www.jetbrains.com/teamcity/" target="_blank">TeamCity server</a> for automating pushing out changes from Git to production servers. TeamCity is more often used as a continuous integration and automated build tool which it excels at, but you would be surprised how easy it is to execute custom workflows like deployment automation with TeamCity.<br />PS: We used TeamCity 8.x and steps below might vary a bit if your TeamCity major version is different.</li><li>A <a name="configuration-template" id="configuration-template"></a><em>Configuration Template</em> was created in TeamCity that would be shared by all sites where deployment was to be automated. The template:<ol><li>Named <em>FTP Uploads</em> had default settings for all other options on the <em>General Settings</em> tab.</li><li>Had no <em>VCS Root</em> attached.</li><li>No <em>Build Step</em> specified.</li><li>A <em>VCS Trigger</em> to trigger a build every 300 seconds after a VCS change is detected.<br />This step is important. Our decision was to upload changes to production server 5 minutes after they were committed to the <em>prod</em> branch in Git. If you would rather do this manually, you should not create any Trigger in the <em>Configuration Template</em> then.</li><li>On the Failure conditions tab, the checkbox to <em>Fail Build</em> if <em>an error message is logged by build runner</em> was checked. Everything else was at default.</li><li>A total of 5 <em>System Properties</em> was added on the <em>Parameters</em> tab. These were configuration options for automated FTP uploads to production server and would be discussed in more detail below and futher in a subsequent blog post.</li><li>All other tabs were left at their defaults.</li></ol></li><li>A <a name="build-configuration" id="build-configuration"></a>new build was created in TeamCity for each site where we needed to automate deployment. The build was created using the <em>FTP Uploads</em> <em>Configuration Template</em> detailed in previous step.<br />The build had following properties:<ol><li>A site specific name given on <em>General Settings</em> tab and everything else at default.</li><li>A <em>VCS root</em> created in <em>Version Control Settings</em> tab. The critical aspect is to ensure the <em>VCS root</em> points to the <em>prod</em> branch in your VCS, not the <em>master</em> branch.</li><li>A single <em>PowerShell</em> build step which would upload changes to files in the <em>prod</em> branch over FTP to the production servers.<br />All this step does is creating a <em>PowerShell</em> build runner pointing to <em>TeamCity.FtpRunner.ps1</em> script which is the topic of another <a href="http://www.rahulsingla.com/blog/2014/09/a-powershell-based-ftp-runner-for-teamcity-builds">blog post here</a>.<br />The <em>Script</em> option should be set to <em>File</em> and Script execution mode set to <em>Execute .ps1 script with "-File" argument</em>. Here's a screenshot to avoid any confusion (please click to enlarge the screenshot):<br /><br /><a href="http://www.rahulsingla.com/sites/default/files/content/blog/ftp-deployment-ps-build-step.png" target="_blank"><img src="http://www.rahulsingla.com/sites/default/files/content/blog/ftp-deployment-ps-build-step.png" alt="Ftp Deployment PowerShell Build Step" title="Ftp Deployment PowerShell Build Step" width="200" height="120" /></a> </li><li>Values for FTP <em>System Parameters</em> defined in the <em>Parameters</em> tab. As mentioned already, this requires a bit more details to be explained and is covered in a separate blog post:<br /><a href="http://www.rahulsingla.com/blog/2014/09/a-powershell-based-ftp-runner-for-teamcity-builds">A PowerShell based FTP runner for TeamCity builds</a></li><li>All other tabs left at their defaults.</li></ol><br />This completes what you need to push your local changes to the production server as they are committed to the <em>prod</em> branch in your VCS repo.</li><li>The final part is getting database and/or uploaded content from the production site if the team needs it. This was achieved by creating a Drupal module named <a href="https://github.com/imbibe/DrupalItUtils" target="_blank">it_utils</a> (Utils for Drupal from Imbibe Tech).<br />The module itself is open-sourced with its source on GitHub here:<br /><a href="https://github.com/imbibe/DrupalItUtils">https://github.com/imbibe/DrupalItUtils</a><br /><br />The module provides these features:<ol><li>A single-click option to download the Drupal db backup.</li><li>A single-click option to download file-system backup of Drupal's public files repository.</li><li>A single-click option to download file-system backup of the entire Drupal installation itself.</li></ol></li></ol><p>Once you configure the TeamCity build for pushing out changes and the Drupal module to retrieve content from production, you have a complete 2-way workflow to push and retrieve content.</p><p>I would now again go over each point of the deployment automation objectives to illustrate how the above workflow achieves each of the objectives:</p><ol><li>The entire process does not need any manual access to the server in any way. FTP credentials to upload content via the TeamCity build need to be saved as the build parameters.<br />With TeamCity 8.1 supporting Password type build parameters which aren't shown anywhere in TeamCity's interface once saved, makes saving the credentials with the build configuration secure enough for regular use-cases.<br /><br />If you are still concerned about saving FTP credentials with the build, there are lots of other things you can do (e.g. saving the parameters in Windows Credentials vault etc and retrieving from there in your FTP upload script, but such ways are outside the scope of this blog post).<br /><br />You should definitely look at the <a href="http://www.rahulsingla.com/blog/2014/09/a-powershell-based-ftp-runner-for-teamcity-builds">other blog post</a> to see in more detail how build triggered FTP upload works.</li><li>The team can request db/file-system backup.<ol><li>If the client has authorized us, we can fetch it via the <a href="https://github.com/imbibe/DrupalItUtils" target="_blank">it_utils</a> Drupal module mentioned above.</li><li>Or the client can download and send us.</li></ol>In both cases, there is sufficient scope for removing sensitive information from the backups (both database or file-system) if needed before sending to our team.</li><li>The team works in the <em>master</em> branch and all approved changes/enhancements are merged to <em>prod</em> branch. In our case, approval usually comes in-house from a manager, but if needed, you can create a separate build pointed to <em>master</em> branch to send changes to the client for approval before merging in <em>prod</em> branch.</li><li>For upgrading contrib modules, or Drupal itself, we follow the usual process:<ol><li>Put site in maintenance mode.</li><li>Removing existing contrib module or Drupal files from <em>prod</em> branch by making a commit and triggering a build.</li><li>Add updated contrib module or Drupal files and trigger a build.</li><li>Go through Drupal's upgrade wizard<br /><br />Mission accomplished again with no manual access to server required :)</li></ol></li><li>There is sufficient scope for manual oversight in each step as needed (either from in-house managers or client).<ol><li>You can look at changes in <em>master</em> before authorizing <em>prod</em> merge.</li><li>You can auto-trigger scheduled builds for <em>prod</em> branch changes or login to TeamCity and trigger manually.</li><li>You can retrieve data from prod via a web interface and clean/de-sensitive as needed before sending to our team.</li></ol></li></ol><p>All in all, everything working as needed. This has been one long textual blog post and I would add just one more aspect before wrapping it up.</p><p>The entire process is described with a Drupal-centric overtone, but you can apply it to most CMSes or web-deployments where the source code is not compiled and pushed to production in source form (i.e. it can be applied to most PHP-based projects and CMSes as well as other source-form deployed platforms).</p><ol><li>The build process remains the same, you just point at proper end-points.</li><li>The only thing you need to do is to tweak the database or file-system downloads for your CMS or custom project.<br /><br />If you have a look at the <a href="https://github.com/imbibe/DrupalItUtils">it_utils</a> Drupal module, you would notice the actual logic to create backups for either the database or the file-system is written as independent functions in the <a href="https://github.com/imbibe/DrupalItUtils/tree/master/inc">inc folder</a> inside the module. All you need to do is to call the functions via an appropriate event from your project's or CMS's interface. For CMS, it usually means creating a skeleton wrapper module/plugin to call those functions which should be pretty easy.</li></ol><p>I think I would call it a day there. If you have any confusion or would like to get more details on any particular aspect or step described, feel free to ask in comments below.<br />FYI, the process is production tested and running successfully on this site, Imbibe's various sites and a few of our client sites (e.g. <a href="http://geniusclasses.co.in/" target="_blank">GCM Classes</a>).</p><p> </p></div></div></div><div class="field field-name-taxonomy-vocabulary-11 field-type-taxonomy-term-reference field-label-above"><div class="field-label">System:&nbsp;</div><div class="field-items"><div class="field-item even" rel="schema:category"><a href="/category/system/windows/powershell" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">PowerShell</a></div><div class="field-item odd" rel="schema:category"><a href="/category/system/windows/powershell/powershell-2" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">PowerShell 2</a></div></div></div><!-- google_ad_section_end --><div class="field field-name-taxonomy-vocabulary-10 field-type-taxonomy-term-reference field-label-above"><div class="field-label">PHP:&nbsp;</div><div class="field-items"><div class="field-item even" rel="schema:category"><a href="/category/php/php-5-x" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">PHP 5.x</a></div></div></div><div class="field field-name-taxonomy-vocabulary-7 field-type-taxonomy-term-reference field-label-above"><div class="field-label">CMSes:&nbsp;</div><div class="field-items"><div class="field-item even" rel="schema:category"><a href="/category/cmses/drupal" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Drupal</a></div><div class="field-item odd" rel="schema:category"><a href="/category/cmses/drupal/drupal-6-x" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Drupal 6.x</a></div><div class="field-item even" rel="schema:category"><a href="/category/cmses/drupal/drupal-7-x" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Drupal 7.x</a></div></div></div><div class="field field-name-taxonomy-vocabulary-12 field-type-taxonomy-term-reference field-label-above"><div class="field-label">Enterprise Servers:&nbsp;</div><div class="field-items"><div class="field-item even" rel="schema:category"><a href="/category/enterprise-servers/teamcity" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">TeamCity</a></div><div class="field-item odd" rel="schema:category"><a href="/category/enterprise-servers/teamcity/teamcity-8-x" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">TeamCity 8.x</a></div></div></div><span rel="schema:url" resource="/blog/2014/09/a-deployment-strategy-for-drupal-sites" class="rdf-meta element-hidden"></span><span property="schema:name" content="A deployment strategy for Drupal sites" class="rdf-meta element-hidden"></span><ul class="links inline"><li class="addtoany first last"><span><span class="a2a_kit a2a_target addtoany_list" id="da2a_5">
<a class="a2a_dd addtoany_share_save" href="https://www.addtoany.com/share_save#url=https%3A%2F%2Fwww.rahulsingla.com%2Fblog%2F2014%2F09%2Fa-deployment-strategy-for-drupal-sites&amp;title=A%20deployment%20strategy%20for%20Drupal%20sites"><img src="/sites/all/modules/addtoany/images/share_save_171_16.png" width="171" height="16" alt="Share this"/></a>
</span>
</span></li>
</ul>Sun, 14 Sep 2014 16:21:13 +0000rahul235 at https://www.rahulsingla.comhttps://www.rahulsingla.com/blog/2014/09/a-deployment-strategy-for-drupal-sites#commentsMEF - Ordering multiple parts imported using ImportMany attribute of Managed Extensibility Frameworkhttps://www.rahulsingla.com/blog/2013/11/mef-ordering-multiple-parts-imported-using-importmany-attribute-of-managed-extensibilit
<!-- google_ad_section_start --><div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="schema:blogPost content:encoded"><p>Hello World :)</p><p>It's been so long since I last blogged, it essentially feels to be blogging like first time. A lot has happened all this time, and I hope I would be able to share some of my learnings more frequently going forward.</p><p>Switching to the topic of the current blog post, I got a chance to do some serious development with Managed Extensibility Framework recently. And I must say, I was thrilled to see the completely new definition of "extensibility" put forth by this framework; with the degree of easiness, decentralization and dynamicity this framework brings in enabling injection of third-party components in your code.</p><p>In my case, I was developing a sort of ETL (Extract/Transform/Load) utility. It needed to read data from a database, apply various checks and/or transformations to ensure the data was in the desired format; and upon success of all checks, add the data to a second database where it would be processed further by downstream applications.</p><p>We needed to provide extensibility to clients for the checks the data went through to decide whether it should be added to the second database. We were shipping a couple of checks out-of-the-box and clients needed drop-in extensibility for adding new checks as desired. After some research, I came to the conclusion (MEF) Managed Extensibility Framework was the better of the options available for our current use-case.</p><p>As I was planning the implementation with MEF (which basically consisted of just publishing an interface that plugins can implement to perform the check, and importing all such plugins using the <em>ImportMany</em> attribute of MEF), there came the inevitable question of managing the dependencies between various <em>checks</em>. Most of the extensibility we needed to provide was already well demonstrated by the <a href="http://msdn.microsoft.com/en-us/library/dd460648(v=vs.100).aspx#simplecalculator_an_example_application" target="_blank">SimpleCalculator example</a> on MEF's MSDN page. However unlike the example, where the exports that extended the functionality of the Calculator were independent of each other, the exported <em>checks</em> in our case needed to be able to express dependencies between them so as to ensure they are always executed in the right order.</p><p>Out of the box, MEF ships with the assumption that parts (both Imports and Exports) are independent and can function with no knowledge of each other. However it leaves enough room for expressing dependencies and any other custom information by parts if required using <a href="http://msdn.microsoft.com/en-us/library/ee155691(v=vs.100).aspx#metadata_and_metadata_views" target="_blank">Metadata</a>.</p><p>The easiest (and in fact the first) solution that came to my mind was using a numeric Metadata property let's say <em>Position</em>, so the interface definition for metadata (that MEF calls metadata views) would look like:</p><p> </p><p>{syntaxhighlighter brush: csharp;fontsize: 100; first-line: 1; }public interface IPluginMetadata
{
[DefaultValue(1)]
int Position { get; }
}{/syntaxhighlighter}</p><p>Plugins would define their relative ordering using a numeric value for <em>Position</em> metadata property and any plugin that doesn't define Position explicitly can be executed arbitararily in any order between other plugins. Plugins specifying the <em>Position</em> metadata property would be executed in increasing order of the value for this metadata property.</p><p>However as I thought more into this approach, I was not quite satisfied with it. It did not seem to have enough flexibility to allow precise specification of relative ordering of plugins. For example, if someone created a plugin with <em>Position</em> 5 as it needed to be executed after another existing plugin with <em>Position</em> 4, there would be no way a third plugin can be deterministically executed after plugin with <em>Position</em> 4 but before <span style="font-style: italic;">Position</span> 5 plugin. In most cases, this would seem far-fetched and in our case also, we did not need such precise specification of plugin ordering; I was nevertheless interested in finding a better alternative both from an academic perspective; as well as practically too. Recall I mentioned we were shipping 2 <em>checks</em> out of the box for our ETL application (where a <em>check</em> was basically an imported MEF plugin); what if a client later wanted to inject a custom operation between the two being shipped out of the box. This could have happened down the road, and the numerical ordering approach didn't just seem flexible enough and not something that complements the rich extensibility and abstraction offered by the MEF framework.</p><p>As I was thinking and researching more into the problem, I came across the <a href="http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.utilities.orderattribute(v=vs.100).aspx" target="_blank">OrderAttribute</a> from Visual Studio's SDK. And straight away, I very much liked the idea. Give a unique name to each plugin and allow plugins to specify their relative ordering by explicitly communicating to the Host the list of other plugins which should be executed before or after them.</p><p>The <em>OrderAttribute</em> referenced came from Visual Studio's SDK, and I did not want to reference VS assemblies in our solution, so I decided to replicate the functionality inspired from VS. Thus my <em>OrderAttribute</em> looked like this:</p><p> </p><p>{syntaxhighlighter brush: csharp;fontsize: 100; first-line: 1; }[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]
public class OrderAttribute : Attribute
{
#region Public Properties
public string Before { get; set; }
public string After { get; set; }
#endregion
}{/syntaxhighlighter}</p><p> </p><p>The next challenge was the definition of the Metadata view to support the Order metadata attribute. Notice this attribute can be applied multiple times to a plugin class to express its relative dependency to other plugins. And there was no documentation on MSDN detailing how a multiple usage MEF metadata attribute translates to a property in the metadata view. Luckily I got my answer in <a href="http://mef.codeplex.com/wikipage?title=Exports%20and%20Metadata#C25405" target="_blank">a comment</a> on a MEF's documentation page at its codeplex site. And I arrived at the following conclusion:</p><blockquote><p>Multiple custom export attributes can be expressed as an array corresponding to each property of the exported attribute.</p></blockquote><p>Armed with this knowledge, my Metadata view interface looked like the following:</p><p> </p><p>{syntaxhighlighter brush: csharp;fontsize: 100; first-line: 1; }public interface IPluginMetadata
{
string Name { get; }
[DefaultValue(new string[] { })]
string[] Before { get; }
[DefaultValue(new string[] { })]
string[] After { get; }
}{/syntaxhighlighter}</p><p><br />At this moment, I could import multiple plugins for my purposes where the plugins can precisely define their dependencies. Here's one such sample plugin using the Order attribute to define relative dependencies:</p><p> </p><p>{syntaxhighlighter brush: csharp;fontsize: 100; first-line: 1; }[Export(typeof(IPlugin))]
[ExportMetadata("Name", "Company3.Plugin")]
[Order(Before = "Company1.Plugin", After = "Company2.Plugin")]
public class MyPlugin: IPlugin
{
#region IPlugin Members
public void DoSomething ()
{
Console.WriteLine("Hello from Company 3");
}
#endregion
}{/syntaxhighlighter}</p><p>The beauty of the approach is you are able to specify both ways: whether you want your plugin to be executed before another plugin or after another plugin. You can decorate your plugin with with Order attribute multiple times if you have multiple <em>Before</em> or <em>After</em> dependencies (<em>Before</em> dependency meaning you want your plugin to be executed "before" the other plugin specified, and vice versa for the <em>After</em> dependency). So if you want to inject your plugin before another plugin that your client is already using, you do not need to modify the other plugin's code (that might not even be accessible to you). You can configure your plugin to be executed <em>before</em> the other plugin simply by specifying the Order attribute on your plugin class using the other plugin's name as the value for <em>Before</em> property of the attribute.</p><p>You do not need to define both <em>Before</em> and <em>After</em> attribute while decorating your plugin with the Order attribute, you can use only one of these if that is what you need.</p><p>The final piece of the puzzle was ordering the imported plugins in the Host class (i.e. the consumer of these plugins). Surprisingly, this proved more tricky than I initially thought. It actually took more time in writing the logic to re-order imported plugins based on their dependencies specified than it took to assemble this whole order/dependency management infrastructure. The complexity stemmed from the recursive nature of dependencies. So for example, if we have 3 plugins, A, B and C. Where A has a <em>Before</em> dependency on B and <em>After</em> dependency on C, then although there is no explicit relation between plugins B and C, but there is an implicit <em>After</em> relation of B on C (or C has an implicit <em>Before</em> dependency on B). Such implicit dependencies can cascade unexpectedly, as number of plugins as a whole as well as proportion of those having dependencies increase, so it took some good thought and time to conceptualize logic to correctly reorder plugins before they are invoked.</p><p>Here's one sample Host class that can use such plugins and ensure they are invoked in order of their dependencies (if any):</p><p> </p><p>{syntaxhighlighter brush: csharp;fontsize: 100; first-line: 1; }public class Host
{
#region Private Members
[ImportMany]
private IEnumerable&lt;Lazy&lt;IPlugin, IPluginMetadata&gt;&gt; plugins = null;
private List&lt;IPlugin&gt; sortedPlugins = null;
#endregion
#region Public Methods
public void DoWork ()
{
this.ensureParts();
foreach (var plugin in this.sortedPlugins)
{
plugin.DoSomething();
}
}
#endregion
#region Private Methods
private void ensureParts ()
{
if (this.plugins != null)
{
return;
}
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new AssemblyCatalog(typeof(Host).Assembly));
catalog.Catalogs.Add(new DirectoryCatalog(Path.GetDirectoryName(typeof(Host).Assembly.Location)));
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
this.sortedPlugins = this.getSortedParts(this.plugins);
}
private List&lt;IPlugin&gt; getSortedParts (IEnumerable&lt;Lazy&lt;IPlugin, IPluginMetadata&gt;&gt; list)
{
//The code for sorting is available in the sample code attached with this blog post.
}
}
{/syntaxhighlighter}</p><p>Due to the length of logic that orders the plugins before consuming them, I have not re-produced it inline above. However the same is attached with the blog post below.</p><p>The attached sample solution contains 4 projects:</p><ol><li>The <em>ConsoleApplication1</em> project is the Host project that exposes the interface (and associated metadata view) that plugins should implement. It imports those plugins, re-orders them based on the metadata and invokes the plugins in order of their dependencies.<br /><br />The <em>Host</em> class in this project contains the logic for re-ordering plugins that I did not reproduce above.</li><li>The other 3 library projects are very skeletal implementations of the plugin interface primarily demonstrating how to specify the metadata and dependencies between them.</li></ol><p>Please ensure to build the entire solution before trying to run the console application. There are some more points/assumptions you should take note of for this implementation of plugin re-ordering:</p><ul><li>Each plugin is assumed to have a unique name. This is very easy to achieve and in most cases, you can use the fully qualified name of your plugin class (i.e. its full namespace and the class name in regular dot separated notation) as the plugin metadata name.</li><li>Plugins define their <em>Before</em> and/or <em>After</em> dependencies using the unique name of other plugins and names are case-sensitive.</li><li>Ensure there are no circular dependencies between plugins (plugin A specifying B as its <em>Before</em> dependency and B specifying A as its <em>Before</em> dependency creates a circular dependency). I haven't tested the code for this scenario.</li><li>If you choose to specify only one of the <em>Before</em> or <em>After</em> properties for Order attribute on your plugin class, the other property for the Order attribute would have <em>null</em> value. This does not affect you if you are the plugin author.<br /><br />However if your are the plugin consumer (i.e. the author of Host class), you need to be aware that the <em>Before</em> and <em>After</em> arrays obtained from a plugin's metadata can contain <em>null</em> values and you should simply discard these <em>null</em> values without considering them for re-ordering plugins.</li></ul><p> </p></div></div></div><!-- google_ad_section_end --><div class="field field-name-taxonomy-vocabulary-3 field-type-taxonomy-term-reference field-label-above"><div class="field-label">.NET:&nbsp;</div><div class="field-items"><div class="field-item even" rel="schema:category"><a href="/category/net/c" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">C#</a></div><div class="field-item odd" rel="schema:category"><a href="/category/net/vb-net" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">VB.NET</a></div><div class="field-item even" rel="schema:category"><a href="/category/net/visual-studio" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Visual Studio</a></div><div class="field-item odd" rel="schema:category"><a href="/category/net/net-4-0" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">.NET 4.0+</a></div><div class="field-item even" rel="schema:category"><a href="/category/net/net-4-0/net-4-5" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">.NET 4.5+</a></div></div></div><div class="field field-name-upload field-type-file field-label-hidden"><div class="field-items"><div class="field-item even"><table class="sticky-enabled">
<thead><tr><th>Attachment</th><th>Size</th> </tr></thead>
<tbody>
<tr class="odd"><td><span class="file"><img class="file-icon" alt="Package icon" title="application/zip" src="/modules/file/icons/package-x-generic.png" /> <a href="https://www.rahulsingla.com/sites/default/files/content/blog/mef-plugin-reordering.zip" type="application/zip; length=168736" title="mef-plugin-reordering.zip">mef-plugin-reordering.zip</a></span></td><td>164.78 KB</td> </tr>
</tbody>
</table>
</div></div></div><span rel="schema:url" resource="/blog/2013/11/mef-ordering-multiple-parts-imported-using-importmany-attribute-of-managed-extensibilit" class="rdf-meta element-hidden"></span><span property="schema:name" content="MEF - Ordering multiple parts imported using ImportMany attribute of Managed Extensibility Framework" class="rdf-meta element-hidden"></span><ul class="links inline"><li class="addtoany first last"><span><span class="a2a_kit a2a_target addtoany_list" id="da2a_6">
<a class="a2a_dd addtoany_share_save" href="https://www.addtoany.com/share_save#url=https%3A%2F%2Fwww.rahulsingla.com%2Fblog%2F2013%2F11%2Fmef-ordering-multiple-parts-imported-using-importmany-attribute-of-managed-extensibilit&amp;title=MEF%20-%20Ordering%20multiple%20parts%20imported%20using%20ImportMany%20attribute%20of%20Managed%20Extensibility%20Framework"><img src="/sites/all/modules/addtoany/images/share_save_171_16.png" width="171" height="16" alt="Share this"/></a>
</span>
</span></li>
</ul>Thu, 14 Nov 2013 11:40:33 +0000rahul232 at https://www.rahulsingla.comhttps://www.rahulsingla.com/blog/2013/11/mef-ordering-multiple-parts-imported-using-importmany-attribute-of-managed-extensibilit#commentsDebugging issues with payment gateway integrations in development environmentshttps://www.rahulsingla.com/blog/2013/03/debugging-issues-with-payment-gateway-integrations-in-development-environments
<!-- google_ad_section_start --><div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="schema:blogPost content:encoded"><p>Today's the end of a financial year (atleast here in India), so I thought I should write a post related to this sector. Now considering that I have a hardcore technical background, it shouldn't come as a surprise if the post rather gets techno-financial. So here's how it goes.</p><p>If you have ever worked on an eCommerce product or platform and have written custom code to provide integration with any Payment Gateway, you would know its a pain to write code to integrate with a Payment Gateway, and subsequently having to modify, adapt or debug it after it has been pushed to production.</p><p>More than the code or its complexity to integrate with the gateway, the setup of a suitable environment where you can write and debug code for a gateway is a bigger challenge. Most Payment Gateways I have seen (especially those that accept Credit Cards, NetBanking etc) lock down which payment requests are accepted in various ways (for security purposes) often including these constraints:</p><ol><li>Payment gateways typically allow payment initiation from a particular domain only, you normally need to submit the domain name to them as part of the setup process.</li><li>In most of the cases, the absolute url from where payments can be initiated are also fixed. Some gateways allow only a single initiation url while others allow upto 3 (or sometimes more) initiation urls.</li><li>The return url to which the user's browser is redirected back is typically also fixed and has to be submitted to Payment Gateway as part of the setup process.</li><li>If your payment gateway features server to server notifications (where the gateway invokes a url directly back to your server), this notification url also is fixed and submitted to gateway during setup.</li></ol><p>I would exclude PayPal from this discussion, the interesting thing is PayPal does not enforce any of these constraints (in fact all of these are configurable via the form parameters in payment request itself), and yet PayPal is one of the most secure (and successful) payment gateways out there. But these constraints apply to almost all other major Payment Gateways I have coded for, including CitBank's payment gateway, MasterCard's gateway, BillDesk and TechProcess etc. (the last 2 being significant players in India).</p><p>Now each of these constraints poses a problem when you start coding for a gateway, and more so, after you are in production and you need to debug issues or make changes.</p><p>Hard-coded domains and/or urls for payment requests means you cannot use localhost for development, because as soon as you initiate a payment request from localhost, you are bound to see error screens from the gateway telling you your request cannot be processed and most would give you a cryptic error code too.</p><p>Then hard-coded return urls further mean if you are somehow able to initiate payment from your local development environment, you would be redirected to production site when you would return from the gateway back to your site.</p><p>And you practically cannot do anything about this (i.e. somehow make those urls configurable). The only way to change those urls is a length bureaucratic process requiring you to alert your client and request such changes. The client would further ping (most often raise a support ticket with) the payment gateway. The gateway personnel would trigger their internal processes which would most often come back to you for verifications. Sometimes you would need to get escalations. All in all, the entire process could be anywhere from a couple of days to multiple weeks, definitely not the amount of time developers like to keep waiting for making their changes.</p><p>For the purpose of this blog post, I would show you how to bypass the 3 restrictions mentioned above so you can write/change code for payment gatway integrations in development environments without needing to have any portion of the production setup changed. I would enumerate the 3 conditions we would bypass:</p><ol><li>Pre-defined initiation domains for payments</li><li>Pre-defined initiation urls for payments</li><li>Pre-defined return urls from payment gateways</li></ol><p>The fourth constraint mentioned earlier (pre-defined urls for server-to-server notifications) is something you cannot do much about unless you have a way to distinguish between payments initiated from development and production environments using parameters sent by the payment gateway and you can put a proxy or create a routing rule in front of your production site's box that would route notifications for test payments automatically to your dev environments, something which is well outside the scope of this blog post and won't be discussed further.</p><p>I would further discuss 2 scenarios for the development environment:</p><ol><li>One where the dev environment is an exact replica of production</li><li>Another where the dev environment only contains bits and pieces of production and is not an exact replica.</li></ol><p>But first, let's analyze the 3 constraints listed above and see how the payment gateways are able to enforce these rules.</p><p>It might come as a surprise to you that the only way payment gateways can enforce these rules is via information sent by your browser in a HTTP request, specifically the <em>Referer/Origin</em> Http headers. As a web developer, you would know that when you switch between pages in your browser by clicking a link on the current page or by submitting a form on the page, your browser would send the url of the current page to the target location of your clicked link (or submitted form) in a Http header. Gateways would compare the browser-reported Http Referer header with the domains/urls submitted to them for your particular setup and based on the match would accept/reject a payment request.</p><p>As a web developer, this should ring a bell to you. Anything that is submitted by a browser to a server can be manipulated and changed in various ways and at various points.</p><p>So let's now discuss the 2 dev scenarios one by one:</p><p><strong>1) Your dev environment is an exact replica of production</strong><br />Let's assume your production domain is <a href="http://www.mydomain.com">www.mydomain.com</a> and you are developing on a local machine using localhost domain. The rest of the details (specifically the exact urls) are insignificant here as server-relative urls would be identical on production and your development box.</p><p>Now the first thing you need to do is make your local machine respond to ww.mydomain.com, which is actually very simple than it sounds. If you are on Windows, open your hosts file in a text-editor (<em>C:\Windows\System32\drivers\etc\hosts</em>) and add this line towards the bottom of the file:</p><p></p><pre class="brush: plain;fontsize: 100; first-line: 1; ">127.0.0.1 www.mydomain.com</pre><p>If on Linux, please do the same in <em>/etc/hosts</em> file (this is the typical location of this file on Linux distros, please consult your distro documentation if you can't find it here). Please consult your OS documentation for this file if you are using another OS, like Solaris or OS X.</p><p>Next please create a virtual host/website in your web-server software with the Host name set to <a href="http://www.mydomain.com">www.mydomain.com</a> and pointing to code on your local disk. Here's a screenshot for this on IIS 7.x:</p><p style="text-align: center;"><a href="http://www.rahulsingla.com/sites/default/files/content/blog/iis7.x-production-domain-pointing-to-local-code.jpg" target="_blank" title="Click to enlarge"><img src="http://www.rahulsingla.com/sites/default/files/content/blog/iis7.x-production-domain-pointing-to-local-code.jpg" alt="IIS 7.x production domain pointing to local code" title="IIS 7.x production domain pointing to local code" width="100" height="99" /></a></p><p>On Apache, you can create this vhost:</p><p></p><pre class="brush: plain;fontsize: 100; first-line: 1; ">&lt;VirtualHost 127.0.0.1&gt;
ServerAdmin webmaster@www.mydomain.com
DocumentRoot "D:\Projects\Blog\WebTest"
ServerName www.mydomain.com
&lt;Directory "D:\Projects\Blog\WebTest"&gt;
Options Indexes FollowSymLinks
AllowOverride All
Allow from all
&lt;/Directory&gt;
&lt;/VirtualHost&gt;</pre><p>And that should be it, you are all set (you can search on how to do this if you are using another web-server software). For all purposes, your local machine is now <a href="http://www.mydomain.com">www.mydomain.com</a> when accessed from any browser from your local machine only and you can use your favorite development IDE to run, debug and step-through your payment integration code. The beauty of this set-up is when you return from payment gateway and the gateway sends you to a url on <a href="http://www.mydomain.com">www.mydomain.com</a> (via your browser), it would still be your local code that would be executing because <a href="http://www.mydomain.com">www.mydomain.com</a> is your local system for your browser due to the host entry created earlier.</p><p>Please note if your production site is set to use HTTPS for payments (which would be true in more than 90% of the cases), there's an additional step of configuring a certificate for <a href="http://www.mydomain.com">www.mydomain.com</a> on your web-server software. This is pretty easy, please search the web (or leave a comment below), if you need assistance doing so for your web-server.</p><p>That's it for scenario 1), let's turn our attention now to the more complex and challenging scenario 2), where you don't have a replica of production.</p><p> </p><p><strong>2) Your dev environment is significantly different from production setup</strong><br />This can happen for a variety of reasons, I would simply put forward one such scenario I faced.<br />A year and a half back, we inherited legacy portal for <a href="http://www.icsi.edu" target="_blank">ICSI</a>, that we were supposed to revamp and re-develop. However we were supposed to immediately cater to the payment issues on the portal where a majority of payments did not complete successfully.</p><p>Due to various regulatory and technical issues, we did not get a copy of the portal running on live domain. All we had was source code for only those pages from the portal that initiated payments and handled users returning from payment gateway and we were expected to fix any issues with these files.</p><p>More than anything else, the challenge was to create a new mini-portal locally, mimicking the production workflow scenario, which included sending exact payment initiation urls as Referer to payment gateway and handling redirection back from the gateway.</p><p>After some thought, I was able to devise a way to make this work using a proxy between my browser and web-server that would modify reqeusts to make them appear genuine to the gateway and and modify responses to make browser behave the way I needed for testing and debugging. Due to its extensibility and ability to control requests/responses via a variety of rules, I chose <strong>Fiddler</strong> as my proxy tool to accomplish this.</p><p>To avoid ambiguity, I would list sample urls here that we were working with at that time:</p><ol><li>The production initiation url for payments was:<br /><a href="https://www.mydomain.com/StudentMemberPages/ICSIPaygate.aspx">https://www.mydomain.com/StudentMemberPages/ICSIPaygate.aspx</a></li><li>The payment gateway url was:<br /><a href="https://www.citibank.co.in/servlets/TransReq">https://www.citibank.co.in/servlets/TransReq</a> </li><li>Our local url where we were testing fixed versions of the code was:<br /><a href="https://localhost/DesktopModules/Icsi/Payment/CitiPayment.aspx">https://localhost/DesktopModules/Icsi/Payment/CitiPayment.aspx</a></li><li>This is a Citi Payment gateway example and you would know Citi payment gateway uses same url for payment initiation and return back from gateway.</li></ol><p>I started by setting up a HTTPS certificate locally for localhost. Thereafter I opened Fiddler and ensured it was set as system proxy while it was running.</p><p>Now the next thing I needed was when a request is sent to CitiBank, I needed to set a pre-defined Referer header no matter the url sent by browser. For this, I added this rule in OnBeforeRequest method in Fiddler Script (I am assuming you know how Fiddler rules work, if not please check <a href="http://www.fiddlertool.com/fiddler/dev/scriptsamples.asp" target="_blank">this page</a> first):</p><p> </p><p>{syntaxhighlighter brush: csharp;fontsize: 100; first-line: 1; }static function OnBeforeRequest(oSession: Session) {
//CitiBank
if (oSession.HostnameIs("<a href="http://www.test.citibank.co.in">www.test.citibank.co.in</a>") || oSession.HostnameIs("<a href="http://www.citibank.co.in">www.citibank.co.in</a>")) {
oSession.oRequest["Referer"] = "<a href="https://www.mydomain.com/StudentMemberPages/ICSIPaygate.aspx">https://www.mydomain.com/StudentMemberPages/ICSIPaygate.aspx</a>";
oSession.oRequest["Origin"] = "<a href="https://www.mydomain.com">https://www.mydomain.com</a>";
}
}{/syntaxhighlighter}</p><p>This rule would ensure Citi gateway sees the request as originating from the valid initiation url.</p><p>Next I needed rule(s) which would execute my local code when control returns from Citi gateway back to <a href="http://www.mydomain.com">www.mydomain.com</a>. For this, these 2 rules were needed:</p><p> </p><p>{syntaxhighlighter brush: csharp;fontsize: 100; first-line: 1; }static function OnBeforeRequest(oSession: Session) {
//This rule makes the browser to send request to localhost domain during return from payment gateway
if (oSession.isHTTPS &amp;&amp; oSession.HostnameIs("<a href="http://www.mydomain.com">www.mydomain.com</a>")) {
oSession.hostname = "localhost";
}
//This rule changes the path of return url to your desired local url
if (oSession.url == "<a href="http://www.mydomain.com/StudentMemberPages/ICSIPaygate.aspx">www.mydomain.com/StudentMemberPages/ICSIPaygate.aspx</a>") {
oSession.url = "localhost/DesktopModules/Icsi/Payment/CitiPayment.aspx";
}
}{/syntaxhighlighter}</p><p>And when I placed all these 3 rules together inside OnBeforeRequest method, I was able to execute an end-to-end workflow for testing Citi payments on my local machine.</p><p> </p><p>I will take another example here (a more complex one) where initiation and return urls are different (the example is for TechProcess payment gateway):</p><ol><li>The production payment initiation url was:<br /><a href="https://www.mydomain.com/StudentMemberPages/TechProcessPayment.aspx">https://www.mydomain.com/StudentMemberPages/TechProcessPayment.aspx</a></li><li>My local payment initiation url was:<br /><a href="https://localhost/DesktopModules/Icsi/Payment/TechProcessPayment.aspx">https://localhost/DesktopModules/Icsi/Payment/TechProcessPayment.aspx</a> </li><li>The payment gateway url was:<br /><a href="https://www.tpsl-india.in/PaymentGateway/CheckGatewayEnter.jsp">https://www.tpsl-india.in/PaymentGateway/CheckGatewayEnter.jsp</a></li><li>The production return url was:<br /><a href="https://www.mydomain.com/StudentMemberPages/TechProcessAckPrint.aspx">https://www.mydomain.com/StudentMemberPages/TechProcessAckPrint.aspx</a></li><li>My local return url was:<br /><a href="https://localhost/DesktopModules/Icsi/Payment/TechProcessPaymentReturn.aspx">https://localhost/DesktopModules/Icsi/Payment/TechProcessPaymentReturn.aspx</a></li></ol><p>Here are the rules to accomplish a TechProcess payment workflow on local machine (in a single function, please see comments in code below to understand what each rule does):</p><p> </p><p>{syntaxhighlighter brush: csharp;fontsize: 100; first-line: 1; }static function OnBeforeRequest(oSession: Session) {
//TechProcess
//This rule modified the request to make gateway believe it is originating from the actual source
if (oSession.HostnameIs("<a href="http://www.tpsl-india.in">www.tpsl-india.in</a>")) {
oSession.oRequest["Referer"] = "<a href="https://www.mydomain.com/StudentMemberPages/TechProcessPayment.aspx">https://www.mydomain.com/StudentMemberPages/TechProcessPayment.aspx</a>";
oSession.oRequest["Origin"] = "<a href="https://www.mydomain.com">https://www.mydomain.com</a>";
}
//Both rules below modify return from payment gateway.
//This rule makes the browser to send request to localhost domain during return from payment gateway
if (oSession.isHTTPS &amp;&amp; oSession.HostnameIs("<a href="http://www.mydomain.com">www.mydomain.com</a>")) {
oSession.hostname = "localhost";
}
//This rule changes the path of return url to your desired local url
if (oSession.url == "<a href="http://www.mydomain.com/StudentMemberPages/TechProcessAckPrint.aspx">www.mydomain.com/StudentMemberPages/TechProcessAckPrint.aspx</a>") {
oSession.url = "localhost/DesktopModules/Icsi/Payment/TechProcessPaymentReturn.aspx";
}
}{/syntaxhighlighter}</p><p> </p><p>I know this blog post is getting rather lengthy (and doesn't contain enough images to make a more engaging read). So I would try to wrap it up here believing enough pointers have been provided to enable you to customize your environment for wirting/modifying code for payment gateway integrations.</p><p>If you feel a section of this post needs more elaborate explanation or your scenario is not quite covered here and you are not still not sure how to setup your enviornment, please feel free to leave a comment below, and I would either try to update the post to incorporate your scenario or otherwise reply to your comment directly.</p><p> </p></div></div></div><!-- google_ad_section_end --><div class="field field-name-taxonomy-vocabulary-13 field-type-taxonomy-term-reference field-label-above"><div class="field-label">Tags:&nbsp;</div><div class="field-items"><div class="field-item even" rel="schema:keywords"><a href="/category/tags/payment-gateway" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Payment Gateway</a></div></div></div><span property="schema:name" content="Debugging issues with payment gateway integrations in development environments" class="rdf-meta element-hidden"></span><span rel="schema:url" resource="/blog/2013/03/debugging-issues-with-payment-gateway-integrations-in-development-environments" class="rdf-meta element-hidden"></span><ul class="links inline"><li class="addtoany first last"><span><span class="a2a_kit a2a_target addtoany_list" id="da2a_7">
<a class="a2a_dd addtoany_share_save" href="https://www.addtoany.com/share_save#url=https%3A%2F%2Fwww.rahulsingla.com%2Fblog%2F2013%2F03%2Fdebugging-issues-with-payment-gateway-integrations-in-development-environments&amp;title=Debugging%20issues%20with%20payment%20gateway%20integrations%20in%20development%20environments"><img src="/sites/all/modules/addtoany/images/share_save_171_16.png" width="171" height="16" alt="Share this"/></a>
</span>
</span></li>
</ul>Sun, 31 Mar 2013 15:39:12 +0000rahul231 at https://www.rahulsingla.comhttps://www.rahulsingla.com/blog/2013/03/debugging-issues-with-payment-gateway-integrations-in-development-environments#commentsDrupal 7 - Exposing date/time fields in custom tables to Views 3https://www.rahulsingla.com/blog/2013/03/drupal-7-exposing-date-time-fields-in-custom-tables-to-views-3
<!-- google_ad_section_start --><div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="schema:blogPost content:encoded"><p>Finally I am back blogging after half an year. Its been some good time since my last blog entry, but here I am and let's get down to business without much fanfare :)</p><p>So this week came along an interesting problem. The <a href="http://centrefortransition.org/" target="_blank">Centre for Transition</a> site we had been working on went live recently and it was all good. However the client came back with a change request regarding the <a href="http://centrefortransition.org/calendar" target="_blank">Calendar</a> page. I won't go into technical details, but suffice to say that the page is generated by Views 3 with data being fetched from nodes and fields attached to nodes, a pretty standard way of doing things in Drupal.</p><p>However we were also storing some information (schedule) about each node in a custom table. This was necessitated by one to many relationship between each node and schedules. A node basically represented a WorkShop which could have multiple schedules (where each schedule compromised of start/end dates and location among other fields). The change request revolved around exposing start/end dates from each schedule of the WorkShop node on the Calendar page.</p><p>As the Calendar page was created using Views, this meant exposing our custom table and its columns to Views. It should have been an easy task with the extensibility provided by Views, but it took more time than I anticipated due to Views refusing to recognize Date/Time columns from the custom table.</p><p>If you have ever developed with Views before, you would know its pretty straight-forward to expose your tables and its columns to Views using <em>hook_views_data</em>. Because our custom table used node as a referenced entity, our table was an extension of node (and not a base table for Views). So I quickly mustered up the following definition for <em>hook_views_data</em> in our custom module:</p><p> </p><p>{syntaxhighlighter brush: php;fontsize: 100; first-line: 1; }function workshop_views_data() {
$data = array();
$data['workshop_location']['table']['group'] = t('Workshop');
$data['workshop_location']['table']['join'] = array(
// Directly links to node table.
'node' =&gt; array(
'left_field' =&gt; 'nid',
'field' =&gt; 'nid',
),
);
//Definition for other fields here.
//For the purpose of blog post, I am only enumerating the date/time fields.
$data['workshop_location']['start_date'] = array(
'title' =&gt; t('Location Start Date'),
'help' =&gt; t('Location Start Date'),
'field' =&gt; array(
'handler' =&gt; 'views_handler_field_date',
'click sortable' =&gt; TRUE,
),
'filter' =&gt; array(
'handler' =&gt; 'views_handler_filter_date',
),
'argument' =&gt; array(
'handler' =&gt; 'views_handler_argument_date',
'empty field name' =&gt; t('Undated'),
),
'sort' =&gt; array(
'handler' =&gt; 'views_handler_sort_date',
)
);
$data['workshop_location']['end_date'] = array(
'title' =&gt; t('Location End Date'),
'help' =&gt; t('Location End Date'),
'field' =&gt; array(
'handler' =&gt; 'views_handler_field_date',
'click sortable' =&gt; TRUE,
),
'filter' =&gt; array(
'handler' =&gt; 'views_handler_filter_date',
),
'argument' =&gt; array(
'handler' =&gt; 'views_handler_argument_date',
'empty field name' =&gt; t('Undated'),
),
'sort' =&gt; array(
'handler' =&gt; 'views_handler_sort_date',
)
);
//More fields here.
return $data;
}{/syntaxhighlighter}</p><p>But after clearing Drupal caches, changing View definition to incorporate fields from my custom table and refreshing the Calendar page, I was greeted by tons of these warnings:</p><p><span style="color: #8c2e0b; font-family: 'Open Sans'; font-size: 13px; line-height: 22px; background-color: #fef5f1;"><em>Warning</em>: date_timezone_set() expects parameter 1 to be DateTime, boolean given in <em>format_date()</em></span></p><p>Surprised, I started looking at the source code for Drupal's date module to figure out what was going wrong (a point to note is all handlers I have specified in the code above come bundled with Drupal's Date module which we were already using in the mentioned website). One thing that was pretty clear from the error message was something was wrong with the format of dates stored in start_date/end_date columns of our custom table. And this was strange, we were storing date/times in "<em>YYYY-MM-DD hh:mm:ss</em>" format which is the same format used by Date module to store date time values from its own form fields. And Views had no problem handling those fields, while I was getting lots of above mentioned warnings with Views trying to use date fields from our table.</p><p>As I was studying Date module's code, I noticed the module implements some hook and it adds '<em>is date</em>' property on all date handlers and then it had some code to process only those fields which had '<em>is date</em>' set to TRUE. So I changed all my handler definitions to add '<em>is date</em>' property, e.g.</p><p> </p><p></p><pre class="brush: php;fontsize: 100; first-line: 1; "> 'field' =&gt; array(
'handler' =&gt; 'views_handler_field_date',
'click sortable' =&gt; TRUE,
'is date' =&gt; TRUE,
),</pre><p>I then cleared caches and refreshed Calendar page hoping allz well now, only to be greeted by same warnings again. Stumped I spent the following hour and a half studying Date module's code and googling and making changes to my hook definition but nothing would work.</p><p>Then I came across <a href="http://pastebin.com/YWRwqD6R" target="_blank">this</a> PasteBin code listing a portion of CiviCrm's code for Drupal. And I saw a ray of hope there. I spent more time reviewing CiviCrm's Views integration and trying to mimic the same but I would get one or other warnings every time, until taking a cue from CiviCrm, I decided to dump <em>views_handler_field_date</em> and <em>views_handler_filter_date</em> completely in favor of custom <em>field</em> and <em>filter</em> handlers for my table's date columns.</p><p>I, in fact borrowed the following handlers from CiviCrm:</p><ol><li>Field handler:<br />{syntaxhighlighter brush: php;fontsize: 100; first-line: 1; }class workshop_handler_field_datetime extends views_handler_field_date {
/*
* Convert the DATETIME from the database into unixtime then allow
* views_handler_field_date to render as usual.
* Also trick php into thinking the time is in the same timezone, no
* matter the default timezone
*/
function render($values) {
$value = $values-&gt;{$this-&gt;field_alias};
if (is_string($value) &amp;&amp; strpos($value, "-")) {
$value = strtotime($value);
if ($value) {
//$date = new DateTime();
//$date-&gt;setTimestamp($value);
$values-&gt;{$this-&gt;field_alias} = $value;
}
}
return parent::render($values);
}
}{/syntaxhighlighter}<br /><br /></li><li>Filter handler:<br />{syntaxhighlighter brush: php;fontsize: 100; first-line: 1; }/*
* All the comparisons need to be done with SQL DATETIMES and not unixtime
*/
class workshop_handler_filter_datetime extends views_handler_filter_date {
/*
* We use strtotime() to accept a wide range of date inputs and then
* convert the unixtime back to SQL DATETIME before adding the WHERE clause
*/
function op_simple($field) {
$value = intval(strtotime($this-&gt;value['value'], 0));
if (!empty($this-&gt;value['type']) &amp;&amp; $this-&gt;value['type'] == 'offset') {
// keep sign
$value = time() + sprintf('%+d', $value);
}
$value = $this-&gt;format_date($value);
$this-&gt;query-&gt;add_where($this-&gt;options['group'], $field, $value, $this-&gt;operator);
}
function op_between($field) {
if ($this-&gt;operator == 'between') {
$a = intval(strtotime($this-&gt;value['min'], 0));
$b = intval(strtotime($this-&gt;value['max'], 0));
}
else {
$a = intval(strtotime($this-&gt;value['max'], 0));
$b = intval(strtotime($this-&gt;value['min'], 0));
$this-&gt;query-&gt;set_where_group('OR', $this-&gt;options['group']);
}
if ($this-&gt;value['type'] == 'offset') {
$now = time();
// keep sign
$a = $now + sprintf('%+d', $a);
// keep sign
$b = $now + sprintf('%+d', $b);
}
$a = $this-&gt;format_date($a);
$b = $this-&gt;format_date($b);
// %s is safe here because strtotime + format_date scrubbed the input
$this-&gt;query-&gt;add_where($this-&gt;options['group'], $field, $a, '&gt;=');
$this-&gt;query-&gt;add_where($this-&gt;options['group'], $field, $b, '&lt;=');
}
function format_date($unixtime) {
return date("Y-m-d H:i:s", $unixtime);
}
}{/syntaxhighlighter}<br /><br /></li></ol><p>And then I changed my <em>hook_views_data</em> to this:</p><p> </p><p>{syntaxhighlighter brush: as3;fontsize: 100; first-line: 1; }function workshop_views_data() {
$data = array();
$data['workshop_location']['table']['group'] = t('Workshop');
$data['workshop_location']['table']['join'] = array(
// Directly links to node table.
'node' =&gt; array(
'left_field' =&gt; 'nid',
'field' =&gt; 'nid',
),
);
//Definition for other fields here.
//For the purpose of blog post, I am only enumerating the date/time fields.
$data['workshop_location']['start_date'] = array(
'title' =&gt; t('Location Start Date'),
'help' =&gt; t('Location Start Date'),
'field' =&gt; array(
'handler' =&gt; 'workshop_handler_field_datetime',
'click sortable' =&gt; TRUE,
),
'filter' =&gt; array(
'handler' =&gt; 'workshop_handler_filter_datetime',
),
'argument' =&gt; array(
'handler' =&gt; 'views_handler_argument_date',
'empty field name' =&gt; t('Undated'),
),
'sort' =&gt; array(
'handler' =&gt; 'views_handler_sort_date',
)
);
$data['workshop_location']['end_date'] = array(
'title' =&gt; t('Location End Date'),
'help' =&gt; t('Location End Date'),
'field' =&gt; array(
'handler' =&gt; 'workshop_handler_field_datetime',
'click sortable' =&gt; TRUE,
),
'filter' =&gt; array(
'handler' =&gt; 'workshop_handler_filter_datetime',
),
'argument' =&gt; array(
'handler' =&gt; 'views_handler_argument_date',
'empty field name' =&gt; t('Undated'),
),
'sort' =&gt; array(
'handler' =&gt; 'views_handler_sort_date',
)
);
//More fields here.
return $data;
}{/syntaxhighlighter}</p><p>And bingo, clearing cache one more time and refreshing the Calendar page gave me the results I needed (thanks CiviCrm!!).</p><p>If you observe the above handlers, you would notice all they do is inherit Date module's corresponding handlers and add some code to recognize date times in "<span style="font-style: italic;">YYYY-MM-DD hh:mm:ss</span>" format. What I cannot understand is why native Date module handlers cannot recognize this format when Entity Date fields themselves are stored in this format, good grief!!</p><p>Anyways, for completeness sake, I have attached the source code for these 2 handlers below (DISCLAIMER: they are verbatim copies of corresponding handlers from CiviCrm).</p><p>And yes, do not forget to specify these handlers in your module's .info file (the example below assumes you placed the files for these handlers in "<em>views</em>" sub-directory inside your module's main directory):</p><p></p><pre class="brush: plain;fontsize: 100; first-line: 1; ">files[] = views/workshop_handler_field_datetime.inc
files[] = views/workshop_handler_filter_datetime.inc</pre><p> </p></div></div></div><!-- google_ad_section_end --><div class="field field-name-taxonomy-vocabulary-10 field-type-taxonomy-term-reference field-label-above"><div class="field-label">PHP:&nbsp;</div><div class="field-items"><div class="field-item even" rel="schema:category"><a href="/category/php/php-5-x" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">PHP 5.x</a></div></div></div><div class="field field-name-taxonomy-vocabulary-7 field-type-taxonomy-term-reference field-label-above"><div class="field-label">CMSes:&nbsp;</div><div class="field-items"><div class="field-item even" rel="schema:category"><a href="/category/cmses/drupal" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Drupal</a></div><div class="field-item odd" rel="schema:category"><a href="/category/cmses/drupal/drupal-7-x" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Drupal 7.x</a></div><div class="field-item even" rel="schema:category"><a href="/category/cmses/drupal/views" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Views</a></div><div class="field-item odd" rel="schema:category"><a href="/category/cmses/drupal/views/views-3-x" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Views 3.x</a></div></div></div><div class="field field-name-upload field-type-file field-label-hidden"><div class="field-items"><div class="field-item even"><table class="sticky-enabled">
<thead><tr><th>Attachment</th><th>Size</th> </tr></thead>
<tbody>
<tr class="odd"><td><span class="file"><img class="file-icon" alt="Binary Data" title="application/octet-stream" src="/modules/file/icons/application-octet-stream.png" /> <a href="https://www.rahulsingla.com/sites/default/files/content/blog/workshop_handler_field_datetime._inc" type="application/octet-stream; length=684" title="workshop_handler_field_datetime.inc">workshop_handler_field_datetime.inc</a></span></td><td>684 bytes</td> </tr>
<tr class="even"><td><span class="file"><img class="file-icon" alt="Binary Data" title="application/octet-stream" src="/modules/file/icons/application-octet-stream.png" /> <a href="https://www.rahulsingla.com/sites/default/files/content/blog/workshop_handler_filter_datetime._inc" type="application/octet-stream; length=1647" title="workshop_handler_filter_datetime.inc">workshop_handler_filter_datetime.inc</a></span></td><td>1.61 KB</td> </tr>
</tbody>
</table>
</div></div></div><span rel="schema:url" resource="/blog/2013/03/drupal-7-exposing-date-time-fields-in-custom-tables-to-views-3" class="rdf-meta element-hidden"></span><span property="schema:name" content="Drupal 7 - Exposing date/time fields in custom tables to Views 3" class="rdf-meta element-hidden"></span><ul class="links inline"><li class="addtoany first last"><span><span class="a2a_kit a2a_target addtoany_list" id="da2a_8">
<a class="a2a_dd addtoany_share_save" href="https://www.addtoany.com/share_save#url=https%3A%2F%2Fwww.rahulsingla.com%2Fblog%2F2013%2F03%2Fdrupal-7-exposing-date-time-fields-in-custom-tables-to-views-3&amp;title=Drupal%207%20-%20Exposing%20date%2Ftime%20fields%20in%20custom%20tables%20to%20Views%203"><img src="/sites/all/modules/addtoany/images/share_save_171_16.png" width="171" height="16" alt="Share this"/></a>
</span>
</span></li>
</ul>Sat, 30 Mar 2013 12:07:08 +0000rahul230 at https://www.rahulsingla.comhttps://www.rahulsingla.com/blog/2013/03/drupal-7-exposing-date-time-fields-in-custom-tables-to-views-3#commentsDigitally sign and verify PDF documents in C# using iTextSharp 5.3.x libraryhttps://www.rahulsingla.com/blog/2012/09/digitally-sign-and-verify-pdf-documents-in-c-using-itextsharp-5-3-x-library
<!-- google_ad_section_start --><div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="schema:blogPost content:encoded"><p>While working on a project, we recently came across a requirement to be able to digitally sign pdf documents in C# code using a public/private key pair and later be able to verify the signature. Basically we were working on an online e-Tendering portal for a semi-government organization. The organization wanted to accept documents from its Vendors online through a portal and be able to verify that the documents indeed originated from a particular Vendor. I would not go into the entire workflow here which is a bit complex, for simplicity you can assume the organization had the public keys for Vendors already stored in a secure database.</p><p>We were supposed to develop an easy to use desktop application which would allow Vendors to sign their pdf documents intuitively before submitting them to the Institute. Simultaneously a module was supposed to created in the organization's portal that would allow Vendors to upload such signed documents, which would be verified for their signatures before being forwarded to various departments for processing.</p><p>We had been using the PdfSharp library extensively for various projects (one of which involved some complex pdf manipulation) with very good results. So when I took up this task today morning, I first started browsing PdfSharp's API in hope of finding the classes and methods needed to sign and verify pdf documents. But I was to be disappointed, as I neither found anything in the API nor googling revealed any such feature in Pdfsharp library.</p><p>But I discovered the <a href="http://sourceforge.net/projects/itextsharp/" target="_blank">iTextSharp</a> library in my Googling session which seemed to provide this feature. I downloaded the latest version (5.3.2 at the time of writing this blog post) but browsing the API up-front was not very meaningful. I again turned to Google which threw up <a href="http://itextpdf.sourceforge.net/howtosign.html" target="_blank">this</a> and <a href="http://www.codeproject.com/Articles/14488/E-signing-PDF-documents-with-iTextSharp" target="_blank">this</a> link with code samples for signing documents using iTextSharp. The first of these links was in Java while the second was in .Net. As I started adapting the code from these links, I realized many of the classes and methods being invoked were not present in my version of itextsharp.dll assembly. Somehow I assembled code for signing the document which seemed to work fine. And the signature information was visible when opening the document in a PDF reader.</p><p>As I approched the next step of verifying the signature (with code adapted from <a href="http://itextpdf.sourceforge.net/howtosign.html#howtoverifycs" target="_blank">here</a>), I was unable to verify the signature successfully even after making numerous revisions to the code. And I was back to square zero, signing a document without being able to verify the signature was pretty much useless.</p><p>I again went back googling. It was pretty much clear to me at this moment that iTextSharp library had undergone some considerable changes, and I needed code samples that would work with its latest version. Some frantic searching finally brought me over to the online version of the second edition of "iText in Action" <a href="http://itextpdf.com/book/index.php" target="_blank">book</a> and then to <a href="http://itextpdf.com/examples/iia.php?id=222" target="_blank">this page</a> from Chapter 12 of this book. I was pretty much sure now I had found what I needed.</p><p>Although the code samples were in Java and some aspects of the API are different in its .Net port, I was able to adapt the code from there and have it work in C#.</p><p>Without more of this introduction, I would now allow you to get your hands dirty with the actual code for signing a document using iTextSharp 5.3.x in .Net:</p><p> </p><p>{syntaxhighlighter brush: csharp;fontsize: 100; first-line: 1; }/// &lt;summary&gt;
/// Signs a PDF document using iTextSharp library
/// &lt;/summary&gt;
/// &lt;param name="sourceDocument"&gt;The path of the source pdf document which is to be signed&lt;/param&gt;
/// &lt;param name="destinationPath"&gt;The path at which the signed pdf document should be generated&lt;/param&gt;
/// &lt;param name="privateKeyStream"&gt;A Stream containing the private/public key in .pfx format which would be used to sign the document&lt;/param&gt;
/// &lt;param name="keyPassword"&gt;The password for the private key&lt;/param&gt;
/// &lt;param name="reason"&gt;String describing the reason for signing, would be embedded as part of the signature&lt;/param&gt;
/// &lt;param name="location"&gt;Location where the document was signed, would be embedded as part of the signature&lt;/param&gt;
public static void signPdfFile (string sourceDocument, string destinationPath, Stream privateKeyStream, string keyPassword, string reason, string location)
{
Pkcs12Store pk12=new Pkcs12Store(privateKeyStream, keyPassword.ToCharArray());
privateKeyStream.Dispose();
//then Iterate throught certificate entries to find the private key entry
string alias=null;
foreach (string tAlias in pk12.Aliases)
{
if (pk12.IsKeyEntry(tAlias))
{
alias = tAlias;
break;
}
}
var pk=pk12.GetKey(alias).Key;
// reader and stamper
PdfReader reader = new PdfReader(sourceDocument);
using (FileStream fout = new FileStream(destinationPath, FileMode.Create, FileAccess.ReadWrite))
{
using (PdfStamper stamper = PdfStamper.CreateSignature(reader, fout, '\0'))
{
// appearance
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
//appearance.Image = new iTextSharp.text.pdf.PdfImage();
appearance.Reason = reason;
appearance.Location = location;
appearance.SetVisibleSignature(new iTextSharp.text.Rectangle(20, 10, 170, 60), 1, "Icsi-Vendor");
// digital signature
IExternalSignature es = new PrivateKeySignature(pk, "SHA-256");
MakeSignature.SignDetached(appearance, es, new X509Certificate[] { pk12.GetCertificate(alias).Certificate }, null, null, null, 0, CryptoStandard.CMS);
stamper.Close();
}
}
}{/syntaxhighlighter}</p><p>The VSDoc comments at the top of the function should pretty much explain everything about the input parameters of this method to be able to call it.</p><p> </p><p>And here's the counter-part, a method to verify the signature of a previously signed PDF document:</p><p> </p><p>{syntaxhighlighter brush: csharp;fontsize: 100; first-line: 1; }/// &lt;summary&gt;
/// Verifies the signature of a prevously signed PDF document using the specified public key
/// &lt;/summary&gt;
/// &lt;param name="pdfFile"&gt;a Previously signed pdf document&lt;/param&gt;
/// &lt;param name="publicKeyStream"&gt;Public key to be used to verify the signature in .cer format&lt;/param&gt;
/// &lt;exception cref="System.InvalidOperationException"&gt;Throw System.InvalidOperationException if the document is not signed or the signature could not be verified&lt;/exception&gt;
public static void verifyPdfSignature (string pdfFile, Stream publicKeyStream)
{
var parser=new X509CertificateParser();
var certificate=parser.ReadCertificate(publicKeyStream);
publicKeyStream.Dispose();
PdfReader reader = new PdfReader(pdfFile);
AcroFields af = reader.AcroFields;
var names = af.GetSignatureNames();
if (names.Count == 0)
{
throw new InvalidOperationException("No Signature present in pdf file.");
}
foreach (string name in names)
{
if (!af.SignatureCoversWholeDocument(name))
{
throw new InvalidOperationException(string.Format("The signature: {0} does not covers the whole document.", name));
}
PdfPKCS7 pk = af.VerifySignature(name);
var cal = pk.SignDate;
var pkc = pk.Certificates;
if (!pk.Verify())
{
throw new InvalidOperationException("The signature could not be verified.");
}
if (!pk.VerifyTimestampImprint())
{
throw new InvalidOperationException("The signature timestamp could not be verified.");
}
Object[] fails = CertificateVerification.VerifyCertificates(pkc, new X509Certificate[] { certificate }, null, cal);
if (fails != null)
{
throw new InvalidOperationException("The file is not signed using the specified key-pair.");
}
}
}{/syntaxhighlighter}</p><p>Again the VSDoc comments should explain how to call this method.</p><p>This code was assembled quickly and can certainly be improved (e.g, allow multiple iterations of signing with different public keys and corresponding verification, use a custom image for signature). Nevertheless, it should provide a base for quickly starting to use .Net's version of iTextsharp library and building from here. You would fine <a href="http://itextpdf.com/book/index.php" target="_blank">iText in Action</a> book very helpful for real-world examples and explanation on how to use this library.</p><p> </p></div></div></div><!-- google_ad_section_end --><div class="field field-name-taxonomy-vocabulary-13 field-type-taxonomy-term-reference field-label-above"><div class="field-label">Tags:&nbsp;</div><div class="field-items"><div class="field-item even" rel="schema:keywords"><a href="/taxonomy/term/256" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">itextsharp sign pdf</a></div><div class="field-item odd" rel="schema:keywords"><a href="/taxonomy/term/257" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">itextsharp digital signature</a></div><div class="field-item even" rel="schema:keywords"><a href="/taxonomy/term/258" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">sign pdf c#</a></div><div class="field-item odd" rel="schema:keywords"><a href="/taxonomy/term/259" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">c# digital signature example</a></div><div class="field-item even" rel="schema:keywords"><a href="/taxonomy/term/260" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">attach digital signature to pdf c# using itextsharp</a></div><div class="field-item odd" rel="schema:keywords"><a href="/taxonomy/term/261" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">c# sign pdf</a></div><div class="field-item even" rel="schema:keywords"><a href="/taxonomy/term/262" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">iexternalsignature</a></div><div class="field-item odd" rel="schema:keywords"><a href="/taxonomy/term/263" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">itextsharp c# examples</a></div><div class="field-item even" rel="schema:keywords"><a href="/taxonomy/term/264" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">itextsharp examples c#</a></div><div class="field-item odd" rel="schema:keywords"><a href="/taxonomy/term/265" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">itextsharp signature</a></div><div class="field-item even" rel="schema:keywords"><a href="/taxonomy/term/266" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">pdfsharp html to pdf c# example</a></div><div class="field-item odd" rel="schema:keywords"><a href="/taxonomy/term/267" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">signing a pdf</a></div><div class="field-item even" rel="schema:keywords"><a href="/taxonomy/term/268" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">digitally signed pdf</a></div><div class="field-item odd" rel="schema:keywords"><a href="/taxonomy/term/269" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">c# pdf</a></div><div class="field-item even" rel="schema:keywords"><a href="/taxonomy/term/270" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">makesignature.signdetached</a></div><div class="field-item odd" rel="schema:keywords"><a href="/taxonomy/term/271" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">c# digital signature</a></div><div class="field-item even" rel="schema:keywords"><a href="/taxonomy/term/272" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">pdfsharp digital signature</a></div><div class="field-item odd" rel="schema:keywords"><a href="/taxonomy/term/273" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">pdf signing</a></div><div class="field-item even" rel="schema:keywords"><a href="/taxonomy/term/274" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">sign pdf documents online</a></div><div class="field-item odd" rel="schema:keywords"><a href="/taxonomy/term/275" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">signing a pdf document</a></div><div class="field-item even" rel="schema:keywords"><a href="/taxonomy/term/276" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">c# pdf library</a></div><div class="field-item odd" rel="schema:keywords"><a href="/taxonomy/term/277" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">signature on pdf</a></div><div class="field-item even" rel="schema:keywords"><a href="/taxonomy/term/278" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">signing pdf documents</a></div><div class="field-item odd" rel="schema:keywords"><a href="/taxonomy/term/279" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">digital signature in c#</a></div><div class="field-item even" rel="schema:keywords"><a href="/taxonomy/term/280" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">digital signature c#</a></div><div class="field-item odd" rel="schema:keywords"><a href="/taxonomy/term/281" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">c# itextsharp</a></div><div class="field-item even" rel="schema:keywords"><a href="/taxonomy/term/282" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">itextsharp library</a></div><div class="field-item odd" rel="schema:keywords"><a href="/taxonomy/term/283" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">the digital signature of the object did not verify.</a></div><div class="field-item even" rel="schema:keywords"><a href="/taxonomy/term/284" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">digital signature implementation in c#</a></div><div class="field-item odd" rel="schema:keywords"><a href="/taxonomy/term/285" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">c# electronic signature</a></div><div class="field-item even" rel="schema:keywords"><a href="/taxonomy/term/286" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">digital signing pdf</a></div><div class="field-item odd" rel="schema:keywords"><a href="/taxonomy/term/287" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">itextsharp pdf to image c#</a></div><div class="field-item even" rel="schema:keywords"><a href="/taxonomy/term/288" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">pdfsharp vs itextsharp</a></div><div class="field-item odd" rel="schema:keywords"><a href="/taxonomy/term/289" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">digitally sign documents</a></div><div class="field-item even" rel="schema:keywords"><a href="/taxonomy/term/290" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">itextsharp.text.rectangle</a></div><div class="field-item odd" rel="schema:keywords"><a href="/taxonomy/term/291" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">c# signing</a></div><div class="field-item even" rel="schema:keywords"><a href="/taxonomy/term/292" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">itextsharp tutorial c#.net</a></div><div class="field-item odd" rel="schema:keywords"><a href="/taxonomy/term/293" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">signature defined. must be closed in pdfsignatureappearance.</a></div><div class="field-item even" rel="schema:keywords"><a href="/taxonomy/term/294" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">c# itextsharp tutorial</a></div><div class="field-item odd" rel="schema:keywords"><a href="/taxonomy/term/295" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">itextsharp sign pdf c#</a></div><div class="field-item even" rel="schema:keywords"><a href="/taxonomy/term/296" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">pdf itextsharp</a></div></div></div><div class="field field-name-taxonomy-vocabulary-3 field-type-taxonomy-term-reference field-label-above"><div class="field-label">.NET:&nbsp;</div><div class="field-items"><div class="field-item even" rel="schema:category"><a href="/category/net/net-2-0" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">.NET 2.0+</a></div><div class="field-item odd" rel="schema:category"><a href="/category/net/c" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">C#</a></div></div></div><span rel="schema:url" resource="/blog/2012/09/digitally-sign-and-verify-pdf-documents-in-c-using-itextsharp-5-3-x-library" class="rdf-meta element-hidden"></span><span property="schema:name" content="Digitally sign and verify PDF documents in C# using iTextSharp 5.3.x library" class="rdf-meta element-hidden"></span><ul class="links inline"><li class="addtoany first last"><span><span class="a2a_kit a2a_target addtoany_list" id="da2a_9">
<a class="a2a_dd addtoany_share_save" href="https://www.addtoany.com/share_save#url=https%3A%2F%2Fwww.rahulsingla.com%2Fblog%2F2012%2F09%2Fdigitally-sign-and-verify-pdf-documents-in-c-using-itextsharp-5-3-x-library&amp;title=Digitally%20sign%20and%20verify%20PDF%20documents%20in%20C%23%20using%20iTextSharp%205.3.x%20library"><img src="/sites/all/modules/addtoany/images/share_save_171_16.png" width="171" height="16" alt="Share this"/></a>
</span>
</span></li>
</ul>Thu, 20 Sep 2012 12:40:54 +0000rahul229 at https://www.rahulsingla.comhttps://www.rahulsingla.com/blog/2012/09/digitally-sign-and-verify-pdf-documents-in-c-using-itextsharp-5-3-x-library#commentsExt.Net Beginner FAQhttps://www.rahulsingla.com/blog/2012/09/ext-net-beginner-faq
<!-- google_ad_section_start --><div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="schema:blogPost content:encoded"><p>Its been a good time since I have blogged anything directly related to Ext.Net (I have written ExtJs blog posts in the interim, but nothing that relates primarily to Ext.Net). I finally got an opportunity today to break my silence on Ext.Net.</p><p>A close acquaintance (who is ages ahead of me in terms of IT expertise and experience) recently started using Ext.Net for some project (in fact my guess is today was his first day). And I got a series of calls from him for some help on things he was getting stuck with as he was diving into Ext.Net. It then appeared to me that the sequence of calls can actually be penned down in a blog post for other beginners too, so here are the questions and their replies.</p><p> </p><ol><li><strong>Q:</strong> How to show data from database in &lt;ext:ComboBox&gt;?<br /><strong>A:</strong> The simplest way to start is to bind an IDataReader to ComboBox's Store. This example should help:<br /><a href="http://examples.ext.net/#/GridPanel/System.Data/DataReader/" target="_blank">http://examples.ext.net/#/GridPanel/System.Data/DataReader/</a><br /><br />When you get the hang of things, you would realize you can bind almost any collection (List, DataTable etc.) to a Store. As Ext.Net/ExtJs is MVC based, data is bound to a store and UI controls pick their data from underlying stores.<hr /></li><li><strong>Q:</strong> The referenced example is using X.IsAjaxRequest property, where do I find the X class?<br /><strong>A:</strong> Ext.Net namespace, either import it at top of file, or use Ext.Net.X.IsAjaxRequest.<hr /></li><li><strong>Q:</strong> After binding a ComboBox store to data, I initially want a particular value to be selected when Page renders. I tried a couple of things (<em>Select</em> method in ComboBox etc) but did not work out.<br /><strong>A:</strong> Use ComboBox's <em>SetValue</em> method, passing in the value (not the display text) that should be selected.<hr /></li><li><strong>Q:</strong> I tried disabling a control with Enabled="False" in markup/code-behind, but did not work.<br /><strong>A:</strong> Becuase of the way ASP.NET natively handles <strong>Enabled</strong>/<strong>Visible</strong> property on all controls, it is advisable to NEVER use them with Ext.Net controls. Use <strong>Disabled</strong>/<strong>Hidden</strong> properties instead.<hr /></li><li><strong>Q:</strong> I am trying to handle the Change DirectEvent server-side on <em>ComboBox</em>, but how do I know the method signature for the event.<br /><strong>A:</strong> Visual Studio's <em>Object Browser</em> is your friend. Open <strong>Object Browser</strong>, and then open Ext.Net assembly and goto Ext.Net namespace. You would find a <em>ComboBoxDirectEvents</em> class (in fact you would find a DirectEvents class for all UI controls that have atleast one DirectEvent available). Goto that class and select "Change" from the right-hand list member list. Click <em>Ext.Net.ComponentDirectevent</em> link in Member detail panel, then goto <em>Event</em> event in member list. Finally clicking the <em>Ext.Net.ComponentDirectEvent.DirectEventHandler</em> in member detail panel will show you the signature for the event in member detail panel itself.<br />An alternative approach is to use Source Code Editor Context Menu's <em>Go To Definiton</em> option to browse this same sequence.<br />Additonally its often very helpful to keep Ext.Net's Visual Studio's xml documentation file in your project's bin folder during development for intellisense tips.<hr /></li><li><strong>Additional Tip</strong>: Prefer ComboBox's <em>Select</em> DirectEvent over <em>Change</em> DirectEvent. <em>Change</em> would fire after ComboBox loses focus, but in most cases, you want to respond to actual selection change without waiting for the component to loose focus. <em>Select</em> is the DirectEvent you need to use then.<hr /></li><li><strong>Q:</strong> Okay I was able to find definition for <em>Select</em> DirectEvent and added my Event listener. But I find nothing in <em>Ext.Net.DirectEventArgs</em>. I was expecting the ComboBox's selected value in that object.<br /><strong>A:</strong> Use ComboBox's SelectedItem, SelectedIndex, Value etc. properties.<hr /></li><li><strong>Q:</strong> What is contained in the <em>Ext.Net.DirectEventArgs</em> object then.<br /><strong>A:</strong> It is normally used when passing additional information from client, e.g. start/limit parameters in a Grid paging operation. You can also pass additional custom values from client through that object.<hr /></li><li><strong>Q:</strong> Are DirectEvents invoked via Ajax?<br /><strong>A:</strong> Yes, DirectEvents use XmlHttpRequest and are hence Ajax Requests.<hr /></li><li><strong>Q:</strong> What's is <em>MultiSelect</em> component for?<br /><strong>A:</strong> Its your classic ListBox for enablng multiple selections from a list.<hr /></li><li><strong>Q:</strong> Let's say I have First Name/Last Name search options. I want to enable user search and display results. How should I organize the layout.<br /><strong>A:</strong> While there's a no one size fits all strategy, the usual way of doing it is to use a BorderLayout with a panel in north region containing the search options and a GridPanel in center region for the results.<br />Add an Ext button for search, and in its Click DirectEvent, perform the search and bind GridPanel's store.<hr /></li><li><strong>Q:</strong> I was rather thinking of using <em>MultiSelect</em> instead of <em>GridPanel</em> for results.<br /><strong>A:</strong> I asked him whether his results had one or multiple columns. He said there were 2 columns (First and Last names), but he wanted to show them comma-separated in a single column.<br />My reply was use <em>MultiSelect</em> then, and either add a DisplayName property to your objects and bind list to use this property or use a XTemplate to configure MultiSelect display. For beginners, adding a DisplayName property to their objects would be the easier solution (it takes time to understand ExtJs' XTemplates).<hr /></li><li><strong>Q:</strong> Okay what if I use GridPanel for results, how would I user select multiple rows in a GridPanel?<br /><strong>A:</strong> Specify a <em>RowSelectionModel</em> for GridPanel and set its <em>SingleSelect</em> property value to false (default is true). This example should help:<br /><a href="http://examples.ext.net/#/GridPanel/Selection_Models/Row_Selection/" target="_blank">http://examples.ext.net/#/GridPanel/Selection_Models/Row_Selection/<hr /></a></li><li><strong>Q:</strong> Okay things are getting there now. I want First/Last name search textboxes to appear in a single row. I am using <em>AnchorLayout</em>.<br /><strong>A:</strong> Use <em>CompositeField,</em> search Examples explorer.<hr /></li><li><strong>Q:</strong> There's no CompositeField in Exmaples Explorer.<br /><strong>A:</strong> Oops, he was on Ext.Net 2.0. So please use <em>FieldContainer</em> instead:<br /><a href="http://examples.ext.net/#/Form/FieldContainer/Overview/" target="_blank">http://examples.ext.net/#/Form/FieldContainer/Overview/<hr /></a></li><li><strong>Q:</strong> I want to perform an action when a GridPanel row is selected.<br /><strong>A:</strong> Use Row Selection Listeners/DirectEvents on GridPanel or RowSelectionModel. For custom actions, use CommandColumn:<br /><a href="http://examples.ext.net/#/GridPanel/Commands/Row_Command/" target="_blank">http://examples.ext.net/#/GridPanel/Commands/Row_Command/</a> </li></ol><p> </p><p>A common theme in all the questions and answers was persistent pointing towards the wonderful Examples Explorer for Ext.Net. Although it does not answer everything, it shows how to start (in fact, much more than that).</p><p>Please feel free to ask more questions below, and I will try to add them to the FAQ here. But please take care to keep questions at the Beginner's level, I do not want this blog post to mutate to a forum in itself. If I do not find a question to be Beginner level, I might delete the comment without answering it. A simple rule of thumb would be anything that requires a code-example OR beyond 2-5 lines of explanation would not be considered for answering in this blog post.</p><p> </p></div></div></div><!-- google_ad_section_end --><div class="field field-name-taxonomy-vocabulary-9 field-type-taxonomy-term-reference field-label-above"><div class="field-label">Web 2.0:&nbsp;</div><div class="field-items"><div class="field-item even" rel="schema:category"><a href="/category/frameworks/coolite" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Coolite</a></div><div class="field-item odd" rel="schema:category"><a href="/category/web-2-0/ext-net" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Ext.Net</a></div><div class="field-item even" rel="schema:category"><a href="/category/web-2-0/ext-net-1-x" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Ext.Net 1.x</a></div><div class="field-item odd" rel="schema:category"><a href="/category/web-2-0/ext-net-2-x" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Ext.Net 2.x</a></div></div></div><span property="schema:name" content="Ext.Net Beginner FAQ" class="rdf-meta element-hidden"></span><span rel="schema:url" resource="/blog/2012/09/ext-net-beginner-faq" class="rdf-meta element-hidden"></span><ul class="links inline"><li class="addtoany first last"><span><span class="a2a_kit a2a_target addtoany_list" id="da2a_10">
<a class="a2a_dd addtoany_share_save" href="https://www.addtoany.com/share_save#url=https%3A%2F%2Fwww.rahulsingla.com%2Fblog%2F2012%2F09%2Fext-net-beginner-faq&amp;title=Ext.Net%20Beginner%20FAQ"><img src="/sites/all/modules/addtoany/images/share_save_171_16.png" width="171" height="16" alt="Share this"/></a>
</span>
</span></li>
</ul>Sun, 02 Sep 2012 19:31:59 +0000rahul228 at https://www.rahulsingla.comhttps://www.rahulsingla.com/blog/2012/09/ext-net-beginner-faq#comments