Sunday, December 12, 2010

Some tricks for MS Chart control

Recently I was using MSChart control to render different stats. I faced some problems to render TimeStamp on Y axis. Also I realized that it has some properties which are not directly available in intelligence, but are quite useful. So here are some of the problems and tricks/ workaround for those in MSChart Control.

1. Plot Time Stamp / Time Span on Y axis.

I wanted to plot a graph of “response time” for the tasks assigned to the users. I had response time data for each user in seconds. By default MSChart control doesn’t allow you to plot TimeSpan on Y axis. It does allow DateTime but not TimeSpan. One solution to this might be to convert  TimeSpan into DateTime format and format chart labels to show data in HH:MM:SS format.
TimeSpan testSpan = TimeSpan.FromSeconds(X);
statsChart.Series(0).Points.AddY(new DateTime(testSpan.Ticks))
statsChart.AxisY.LabelStyle.Format = "hh:mm:ss";

This works fine as long as your  timespan doesn’t cross 24 hour. If timespan is more than 24 hours we need to put days as well. Now if you try to print day from the date, problem begins. It tries to add days to date  1/1/1900 and as a result we get dates values from 1/1/1900.

Add Custom Labels

One solution to this problem is plot data points on Y axis as seconds and then customize labels to show seconds in the format dd:hh:mm:ss.
MSChart control supports an event called “Customize” in which we can change the labels of plotted points before they are actually rendered.
            if (UserStatsType == StatsType.RespTime)
            {
                CustomLabelsCollection yAxisLabels = ((Chart)sender).ChartAreas[0].AxisY.CustomLabels;
                for (int cnt = 0; cnt < yAxisLabels.Count; cnt++)
                {
                    TimeSpan ts = TimeSpan.FromSeconds(double.Parse(yAxisLabels[cnt].Text));
                    yAxisLabels[cnt].Text = ts.Days.ToString("00") + ":" + ts.Hours.ToString("00") + ":" + ts.Minutes.ToString("00") + ":" + ts.Seconds.ToString("00");
                }
            }

2. 3-D effects for Bar Charts

By default bar charts are rendered as plane one color columns. We can make charts attractive by giving 3-D efforts to the charts. MSChart control supports some custom properties. One of them is “DrawingStyle”
            foreach (var series in statsChart.Series)
                series["DrawingStyle"] = "Cylinder";

Possible values for “DrawingStyle” property are  Cylinder, Emboss, LightToDark, Wedge, Default

3. Bar charts with different colors.

By default all data points in a series are plotted in same color. While creating a series, we can set the color of the points. If you want to plot different points with different colors we can set these colors after adding points as follows.
            Random rnd = new Random();
            foreach (var pt in statsChart.Series[0].Points)
                pt.Color = Color.FromArgb((rnd.Next(0, 255)), (rnd.Next(0, 255)), (rnd.Next(0, 255)));


Tuesday, October 12, 2010

Implemeting Ajax in SharePoint with JSON services

Background

We wanted to show data from some external web service in to web part on SharePoint 2007. As we were accessing external services outside the domain, it was important to make these controls as Ajax controls on SharePoint.

We had following options to implement Ajax controls in SharePoint.

We decided to use JQuery instead of ASP.Net Ajax. Microsoft and JQuery have a long-term agreement and JQuery is the supported javascript platform for future MS projects. So it was clear to use JQuery for implementing Ajax controls in SharePoint.

Initially I started implementation by using PageMethod approach (Reference - http://encosia.com/2008/05/29/using-jquery-to-directly-call-aspnet-ajax-page-methods/ ). It was a decent approach, but PageMethod returns complete HTML which we have to render in some iframe. If we want to do some change in the way results are displayed on UI, or any small cosmetic change we have to change page method. Due to this it is not possible to use results returned by same page method and render it differently (Like the web service). Furthermore each time we call a PageMethod it creates instance of a page which would cause some performance issues.

Other possible options for Ajax implementation.

I sent pagemethod approach to my chief architects and he suggested following options to for implementing AJAX in SharePoint.

1) ASP.NET AJAX - it now has decent integration into SharePoint, but in general I don't recommend ever using it. I feel strongly that ASP.NET AJAX is not a viable AJAX platform and does not make sense for use even in a standard ASP.NET application. I can go into details sometime for anyone who is curious... In general I find that they've done a really bad job creating a solution "for the web" - rather it's all wrapped up in confusing abstractions for new web developers, which get in the way of anyone who actually understands how to write efficient web apps.

2) Callbacks to PageMethods (your code below) This is a decent alternative to ASP.NET AJAX, as it avoids the client-side junk, although it still depends on ASP.NET AJAX being installed for the back end. There are still some issues, though: in order to call back into a method, you must call back into the page, providing complete View State and re-creating the entire page just so that it can handle the callback. I'd prefer not to do this, because there's no reason to incur this cost.

Both of the above may require some extra steps to get them working in SharePoint. I don't know how much of this you've already done, nor how much may be now unnecessary due to service packs, but in order to add ASP.NET AJAX to SharePoint, see: http://sharepoint.microsoft.com/blogs/mike/Lists/Posts/Post.aspx?ID=3 and http://www.codeplex.com/ajaxifymoss

3) Callbacks to WebServices (an example is linked from the article) This makes more sense, since you are making callbacks into methods that are designed for callback speed. You do not need to provide ViewState or instantiate pages, so there is no extra overhead. On the other hand, it does result in some of the functionality for your callback being put into a separate code space (the web service vs. your control.) This isn't necessarily a bad thing, because 1) you end up with new, shared web services available, and 2) you can share the code in a common library that's used by both the web service and the control. Finally, there is some extra effort to provide web service output as JSON, which is a better format than XML for AJAX callbacks, though not required.

4) Callbacks to custom pages or services: similar to #3, this involves calling back to a "service." In this case the service is just a custom ASPX page that provides results in whatever format you'd like. This is the easiest way to work, but it's a bit non-standard and results in a lot of extra ASPX pages that need to be maintained. Plus these services aren't callable by other consumers, because you've built a custom transport. It's possible to alleviate some of these concerns by implementing an HttpHandler that takes care of the callbacks - in fact, that's what AE does. But I'd recommend #3 over this one.

My suggestion is that you proceed with the #3 option - calling back to services, which respond in JSON. Once you've gotten one service working, we can look for a way to make it easier to create new services, and add some infrastructure to our base code.


Calling xml web service from JQuery
With such a good comparison and possible options I started exploring how to call web service from JQuery. Initially I tried to call existing web services from JQuery. They were returning result as xmldocument.
I used following code snippet to call xml web service from JQuery

$(document).ready(function() {
$.ajax({
type: "POST",
url: "http://rahul-ind01/askme/formservice.asmx/GetForm",
data: "{}",
contentType: "application/json; charset=utf-8",
dataType: "xmlDocument",
success: function(msg) {
$('#RSSContent').removeClass('loading');
$('#RSSContent').html(msg);
}
});
});

Above code was returning result as XmlDocument, But we needed Json objects so that we can use results of web service easily in JQuery. Some libraries (http://www.json.org/json.js.) are available which does convert web service output to json format. But I it would be like another wrapper around web service call. some posts on net says that we need to add “ScriptService” attribute to the service and “[ScriptMethod(ResponseFormat = ResponseFormat.Json)]” attribute to the web method to return json output. But I was still not getting Json output. I was missing some .net 3.5 config entries in my web service config.

Web Service returning JSON as well as SOAP objects
Finally I could modify AE web services so that they return SOAP as well as JSON response.

Here are the changes that needs to be done in the web service.
1. Add [ScriptService] attribute for web service class as follows.

[WebService(Namespace = "http://www.askme.com/webservices")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ToolboxItem(false)]
[ScriptService]
public class FormService : AskMeContentService
{
}
2. Add [ScriptMethod(ResponseFormat = ResponseFormat.Json)] attribute for web method as follows

[WebMethod(Description = "Gets a specific Form given the ID. The sub-forms are not included.")]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public Form GetForm(int formID)

There are multiple config entries related to .Net framework 3.5 which needs to be added in web.config of application which calls these web service from JQuery. Appendix contains additional entries added in web.config.

Following is the sample Jquery code which calls this web service which returns JSON objects.

$(function() {
$.ajax({
type: "POST",
url: "http://rahul-ind01/askme/formservice.asmx/GetForm",
data: '{formID:4991}',
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(msg) {
alert(msg.d);
$('#RSSContent').removeClass('loading').html(msg.d.BodyHTML);
},
failure: function(msg) {
debugger;
alert(msg.d);
}
});
});

Actual implementation started.

With web services returning JSON objects I started making changes in ASP pages that are deployed on SharePoint server to call web services using Jquery. I faced following problems.
1. Since web service was deployed on different server (AE server) and ASP pages were deployed on SharePoint server I started getting “Permission Denied” error. Problem was browser enforces that you cannot make javascript calls across servers. When you load one server page from “Server1” that creates Javascript, the browser enforces that it can’t make an AJAX callback to Server2, Only back to the original server (pages don't matter, but the server address must be the same.)
2. Our API returns user Id in AE. We find corresponding user in SharePoint by matching name and render image and profile links for that user. To get that user profile in JQuery we will have to call sharepoint profile web services in Jquery and also we need to make these services JSON enabled.

To overcome these issues we needed to have another set of web services which will be deployed in SharePoint server. These services will call AE services, map AE users with SharePoint users profile and return these objects as JSON objects. Web parts and pages deployed in SharePoint will call these services deployed in SharePoint and will render controls using JQuery.



ASP pages, deployed on SharePoint server calls JSON enabled AE services deployed on Same SharePoint server using Jquery. These AE services on SharePoint server calls AE services on AE server, adds user profile and any other required data from SharePoint and returns JSON output to JQuery call, which then renders results in ASP pages.


Web.Config Entries for JSON service.
Following are the config entries that need to be added in SharePoint web.config file for ASP AJAX controls to work properly on MOSS 2007. These are .net framework 3.5 related entries required for JSON enabled web services to return JSON data.

Following entries added under root <configuration> section.

<configSections>
<sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<sectionGroup name="scripting" type="System.Web.Configuration.ScriptingSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<section name="scriptResourceHandler" type="System.Web.Configuration.ScriptingScriptResourceHandlerSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/>
<sectionGroup name="webServices" type="System.Web.Configuration.ScriptingWebServicesSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<section name="jsonSerialization" type="System.Web.Configuration.ScriptingJsonSerializationSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="Everywhere"/>
<section name="profileService" type="System.Web.Configuration.ScriptingProfileServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/><section name="authenticationService" type="System.Web.Configuration.ScriptingAuthenticationServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/>
<section name="roleService" type="System.Web.Configuration.ScriptingRoleServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/>
</sectionGroup>
</sectionGroup>
</sectionGroup>
</configSections>

Following 4 entries under <assemblies> section
<add assembly="System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
<add assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
<add assembly="System.Data.DataSetExtensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
<add assembly="System.Xml.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>

Following 3 entries under <httpHandlers> section

<add verb="*" path="*.asmx" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
<add verb="*" path="*_AppService.axd" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
<add verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" validate="false"/>

Thursday, September 30, 2010

How to change Web Part Title Programmatically in SharePoint 2010?

How to change Web Part Title Programmatically in SharePoint 2010?
Generally, Web part title is set in the web part description file. But sometimes we need to set the web part title programmatically. (e.g. changing web part title after changing a value in drop-down control in the web part). We can change web part title by writing following code in the RenderWebPart method of the web part.
This.title = “MyWebPartTitle”;
After this we need to save the title using following code, otherwise changed title won’t appear on the web part title bar.
this.SaveProperties = true;
But there are some cases where this simple trick doesn’t actually work.
· User who is accessing the web part doesn’t have enough permission to save web part properties, setting this.SaveProperies to true throws Permission Exception. This can be handled by writing following line of code

if (SPContext.Current.Web.DoesUserHavePermissions(SPBasePermissions.UpdatePersonalWebParts))
this.SaveProperties = true;

· Even some times you will find that web part title doesn’t change till web part is re-loaded. You will need to set the value after the constructor and before Render/RenderWebPart. Otherwise, you will be setting it too early or too late, respectively.
There are two scenarios
1. If you do not have a TitleBarWebPart on your page, then you can set web part title by overriding OnPreRender method.
2. You have TitleBarWebPart on your page (as is the case with web part page templates , where you can add/remove web parts from a page).
In this case you can set web part title in web part PreRender method. This is due to the fact the TitleBarWebPart generates unique names of each web part in its PreRender method.

void trendDropDown_SelectedIndexChanged(object sender, EventArgs e)
{
string trendType = ((DropDownList)sender).SelectedItem.Value;
if (trendType == "QuestionTrend")
this.Title = Data for Questions;
else if (trendType == "BestPracticeTrend")
this.Title = Data for BestPractice;
if (SPContext.Current.Web.DoesUserHavePermissions(SPBasePermissions.UpdatePersonalWebParts))
this.SaveProperties = true;
}