Archive for June, 2014

Response.Redirect vs. Response.AddHeader vs. Server.Transfer

All these three are used to redirect the user from one location to another, but there are subtle differences.

Response.Redirect

  • Creates a whole new HTTP request to a new Url.
  • Produces a 302 http status code
  • Equivalent to ‘Temporary Redirect’ and the old Url is retained in search indexes. There won’t be any transfer of metrics.

Response.Redirect

Response.AddHeader on Location

  • Creates a whole new HTTP request to a new Url. (Common with Response.Redirect)
  • Produces a 301 http status code
  • The new Url is indexed instead by searchbot. There will be some loss of page rank / traffic value, but the stats will get transferred over.

Response.AddHeader

Server.Transfer

  • This doesn’t change the current Url
  • An internal transfer is done on the server during execution path without creating a new HttpRequest.
  • FYI, the ‘rewrite’ option in the IIS urlrewrite module does a Server.Transfer.

server-transfer

, ,

Leave a comment

Saving data to the Sitecore WFFM database programmatically

I am sure I am not the first to think that the Sitecore WFFM forms are not really as robust as we might like.
But because of the ready made form reports and marketing advantages, we might still want to leverage the wffm backend database for storing the form information, with an independent form front end.

This post is to show how we can save the form information to the wffm database without using the actions provided on submit.
Say you have your list of fields populated in a list of this class:

    public class WffmField
        {
            public string FieldName { get; set; }
            public string FieldGuid { get; set; }
            public string FieldValue { get; set; }
        }

The field guid would be the guid from sitecore:
2014-06-16_175009

You can then save to the WFFM database:

    // This should be populated with the data you want to send to the WFFM database
    var fields = new List<WffmField>(); 
    var wffmDatabaseFields = fields.Select(GetWFFMDatabaseField).ToList();
    
    Sitecore.Forms.Data.DataManager.InsertForm(
    	formId: new Sitecore.Data.ID("<Form guid here>"),
    	fields: new AdaptedResultList(wffmDatabaseFields),
    	sessionID: AnalyticsTracker.SessionId,
    	data: null);

This data will be available in the form reports now as expected.

2014-06-16_205030

Please note, you will need to add a reference to Sitecore.Forms.Core dll and following are the included namespaces:

using Sitecore.Form.Core.Analytics;
using Sitecore.Form.Core.Client.Data.Submit;
using Sitecore.Form.Core.Controls.Data;

,

2 Comments

Script to shrink t-sql logs for all databases on a server

Often on servers we have way too many databases to shrink logs individually when the server seems to be running low on space.
Here is a script which will in turn generate a script to do this for you.

SELECT name INTO #tempDBs FROM master..sysdatabases
where name not in ('master','tempdb','model','msdb')

DECLARE @CurrentDBName varchar(100)
DECLARE @CurrentDBLog varchar(100)
DECLARE @query varchar(1000)

DECLARE @AllDBsCursor CURSOR
SET @AllDBsCursor = CURSOR FAST_FORWARD
FOR

SELECT DB_NAME(database_id), name FROM sys.master_files WHERE DB_NAME(database_id) in
(select name from #tempDBs) and physical_name like '%ldf'
-- and DB_NAME(database_id) like '%joyce%'

OPEN @AllDBsCursor
FETCH NEXT FROM @AllDBsCursor
INTO @CurrentDBName, @CurrentDBLog
WHILE @@FETCH_STATUS = 0
BEGIN

set @query = 'USE ['+@CurrentDBName+']
GO
ALTER DATABASE ['+ @CurrentDBName +']
SET RECOVERY SIMPLE WITH NO_WAIT
GO
DBCC SHRINKFILE('''+@CurrentDBLog+''', 1) 
GO
ALTER DATABASE ['+@CurrentDBName+']
SET RECOVERY FULL WITH NO_WAIT
GO'

PRINT @query

FETCH NEXT FROM @AllDBsCursor
INTO @CurrentDBName, @CurrentDBLog
END
CLOSE @AllDBsCursor
DEALLOCATE @AllDBsCursor

DROP TABLE #tempDBs

, ,

Leave a comment

Notify sitecore user on account enable

Often when a new user is created (especially if created from the sitecore site – as an extranet user), we might find the need to have an approval process in place to ensure access to credible users only.

You can see more details on creating extranet users for your site at Creating an extended Sitecore user for your site

When the user is being created, you can automatically disable the user initially by adding this before user save:

user.IsApproved = false;

This would disable the user id:
2014-06-15_030924

Now when a user admin of your site, goes and enables the user after review, you might want to intimate the user that their account has been enabled and they now have the privileges that it comes with!

The enable user command which is executed when a user is enabled resides in the App_Config\Commands.config file:

  <command name="usermanager:enable" type="Sitecore.Shell.Framework.Commands.UserManager.Enable,Sitecore.Kernel"/>

You can override this command and add your own functionality to the existing needed functionality for enable which you can bring over from the reflected code of Sitecore.Kernel.dll.

Here’s a sample of getting this done:
Config:

  <command name="usermanager:enable" type="Sitecore72.Classes.EnableUserNotify, Sitecore72"/>

Code (This is reflected code from sitecore.kernel.dll with an additional method call for the notification. You could sure choose to write the code yourself for enable – after all you only need to toggle a user property at the end of the day, but this code will also handle the confirm messages and all error handling for you)

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Net.Mail;
using System.Text;
using System.Web.Security;
using Sitecore.ApplicationCenter.Applications;
using Sitecore.Diagnostics;
using Sitecore.Globalization;
using Sitecore.Shell.Framework.Commands;
using Sitecore.Shell.Framework.Commands.UserManager;
using Sitecore.Text;
using Sitecore.Web.UI.Sheer;
using Sitecore.Web.UI.WebControls;
using Sitecore.Web.UI.XamlSharp.Continuations;

namespace Sitecore72.Classes
{
    [Serializable]
    public class EnableUserNotify : Command, ISupportsContinuation
    {
        /// <summary>
        /// Executes the command in the specified context.
        /// 
        /// </summary>
        /// <param name="context">The context.</param>
        public override void Execute(CommandContext context)
        {
            Assert.ArgumentNotNull((object)context, "context");
            var userName = context.Parameters["username"];
            if (!ValidationHelper.ValidateUserWithMessage(userName))
                return;
            var parameters = new NameValueCollection();
            parameters["username"] = userName;
            ContinuationManager.Current.Start((ISupportsContinuation)this, "Run", new ClientPipelineArgs(parameters));
        }

        /// <summary>
        /// Runs the pipeline.
        /// 
        /// </summary>
        /// <param name="args">The args.</param>
        protected void Run(ClientPipelineArgs args)
        {
            Assert.ArgumentNotNull((object)args, "args");
            ListString listString = new ListString(args.Parameters["username"]);
            if (args.IsPostBack)
            {
                if (!(args.Result == "yes"))
                    return;
                List<string> list = new List<string>();
                string key = string.Empty;
                foreach (string username in listString)
                {
                    MembershipUser user = Membership.GetUser(username);
                    Assert.IsNotNull((object)user, typeof(MembershipUser));
                    try
                    {
                        user.IsApproved = true;
                        Membership.UpdateUser(user);

                        NotifyUser(user);

                        Log.Audit((object)this, "Enable user: {0}", new string[1]
                        {
                          username
                        });
                    }
                    catch (NotImplementedException ex)
                    {
                        key = "Sorry, this feature is not supported by the underlying provider implementation. Please contact the system administration for more details";
                    }
                    catch (NotSupportedException ex)
                    {
                        key = "Sorry, this feature is not supported by the underlying provider implementation. Please contact the system administration for more details";
                    }
                    catch (Exception ex)
                    {
                        list.Add(username);
                        key = ex.Message;
                        Log.Error("User can not be enabled", ex, (object)this);
                    }
                }
                if (list.Count > 0)
                {
                    StringBuilder stringBuilder = new StringBuilder();
                    foreach (string str in list)
                    {
                        stringBuilder.Append('\n');
                        stringBuilder.Append(str);
                    }
                    SheerResponse.Alert(Translate.Text("The following users could not be enabled:\n{0}\nException:\n{1}", (object)((object)stringBuilder).ToString(), (object)Translate.Text(key)), new string[0]);
                }
                AjaxScriptManager.Current.Dispatch("usermanager:refresh");
            }
            else
            {
                if (listString.Count == 1)
                    SheerResponse.Confirm(Translate.Text("Are you sure you want to enable {0}?", new object[1]
          {
            (object) listString[0]
          }));
                else
                    SheerResponse.Confirm(Translate.Text("Are you sure you want to enable these {0} users?", new object[1]
          {
            (object) listString.Count
          }));
                args.WaitForPostBack();
            }
        }

        private void NotifyUser(User user)
        {
            var emailMessage = new MailMessage
            {
                From = new MailAddress(ConfigurationSettings.AppSettings["CompanyFromAddress"]),
                Subject = "You are a privileged member!",
                Body = user.FullName + ", you are now a privileged member of our club!"
            };

            emailMessage.To.Add(user.Email);

            using (var client = new SmtpClient())
            {
                client.Send(emailMessage);
            }
        }
    }
}

The corresponding smtp settings can be as usual found in the config:

  <system.net>
    <mailSettings>
      <smtp>
        <network host="yoursmtphost.com" port="25" userName="" password="" defaultCredentials="true" />
      </smtp>
    </mailSettings>
  </system.net>

, , ,

Leave a comment

Creating an extended Sitecore user for your site

You might want to use the sitecore ASP.NET membership provider Core database to manage users for your sitecore site.

In that case, at some point during your site development, you will come across the need to create a new sitecore user from your site front end.
You might also want to have additional fields as is relevant to your application.
In this example, we are adding user address as additional custom fields.

The user template is available in the Core database of your sitecore instance.
The template is available at: /sitecore/templates/System/Security/User

2014-06-15_011504

You might also want to add a custom status field to be used by a site administrator – for user profile approval.
2014-06-15_012247

This field could point to a list of options also available in the core database:
Sample Template: /sitecore/templates/System/Security/User Profile Data/Profile Status Type
2014-06-15_013127

Options: /sitecore/content/Home/Data/Profile Status Types
2014-06-15_013211

Following is the code you could use to create the user:

Please note the code for default selection of the drop down field in the user template, and also the default ProfileItemId – which will determine which view the user will open in when you access the user from the User Manager console.

public bool CreateUpdateAccount(string firstName, string lastName, string emailAddress, string password,
            string addressLine1, string addressLine2, string city,
			string ddlCountriesText, string ddlStateText, string postalCode)
{
	var formData = new NameValueCollection();
	Sitecore.Security.Accounts.User user = null;

	formData["Name"] = firstName + " " + lastName;
	formData["Email"] = emailAddress;
	formData["Password"] = password;

	user = SecurityHelper.SitecoreSecurity.CreateSitecoreUser(formData);

	if (user != null)
	{
		Sitecore.Security.Authentication.AuthenticationManager.Login(user);

		user.Profile.SetCustomProperty(Constants.UserCustomProperty.AddressLine1, addressLine1);
		user.Profile.SetCustomProperty(Constants.UserCustomProperty.AddressLine2, addressLine2);
		user.Profile.SetCustomProperty(Constants.UserCustomProperty.City, city);
		user.Profile.SetCustomProperty(Constants.UserCustomProperty.Country, ddlCountriesText);
		user.Profile.SetCustomProperty(Constants.UserCustomProperty.Region, ddlStateText);
		user.Profile.SetCustomProperty(Constants.UserCustomProperty.PostalCode, postalCode);

		// Constants.CoreDb.ItemGuid.PendingApproval - contains the GUID of the Pending Approval item in Core DB
		// in the folder: /sitecore/content/Home/Data/Profile Status Types
		user.Profile.SetCustomProperty(Constants.UserCustomProperty.ProfileStatus, Constants.CoreDb.ItemGuid.PendingApproval);

		// Constants.CoreDb.ItemGuid.PendingApproval has the guid of the item in Core at: /sitecore/system/Settings/Security/Profiles/User
		// This is to ensure that the default mode of the site created user has these custom properties.
		user.Profile.ProfileItemId = Constants.CoreDb.ItemGuid.SecurityProfileUser;
		user.Profile.Save();

		return true;
	}
	return false;
}

Security Helper method:

public static User CreateSitecoreUser(NameValueCollection formData)
{
	var fullUsername = Sitecore.Context.Domain.GetFullName(Guid.NewGuid().ToString());
	var user = User.Create(fullUsername, formData["Password"]);

	using (new SecurityDisabler())
	{
		// Constants.CoreDb.ItemGuid.PendingApproval has the guid of the item in Core at: /sitecore/system/Settings/Security/Profiles/User
		user.Profile.ProfileItemId = Constants.CoreDb.ItemGuid.SecurityProfileUser;
		user.Profile.FullName = formData["Name"];
		user.Profile.Email = formData["Email"];
		user.Profile.Save();
	}
	return user;
}

Following is a user as seen from the user manager which was created from a site (note the ‘extranet’ domain)
2014-06-23_215757

2014-06-23_220039

Here is the populated droplink field:
2014-06-23_220100

For user login, the following code can be used:

Sitecore.Security.Authentication.AuthenticationManager.Login(domainName + @"\" + userName, password, false);

To validate user:

System.Web.Security.Membership.ValidateUser(domainName + @"\" + userName, password));

To access the custom fields:

var address1= currentUser.Profile.GetCustomProperty(Constants.UserCustomProperty.AddressLine1);

You can also get the list of users pending approval (if you wanted to for a custom User Admin page):

    public static List<User> GetUsersPendingApproval()
    {
		IFilterable<User> allUsers = UserManager.GetUsers();

		// Constants.CoreDb.ItemGuid.PendingApproval - contains the GUID of the Pending Approval item in Core DB
		// in the folder: /sitecore/content/Home/Data/Profile Status Types
		return allUsers.Where(user => user.Profile.GetCustomProperty("Profile Status") == Constants.CoreDb.ItemGuid.PendingApproval).ToList();
    }

, ,

6 Comments

Wrapping Rich Text Value in paragraph tag in Sitecore

Often we find the situation where the front end requires all text coming in from rich text fields in sitecore to be wrapped in say a <p> tag, for styling purposes.
In most cases, it is a better option to achieve this through code, than leave it up to the content authors.

You could go with one of the following 2 approaches:

You could create a new pipeline event in the

  • <saveRichTextContent> pipeline – This could enable you to append the <p> tag when you hit save on the rich text editor in sitecore
  • <renderField> pipeline – This could on the fly wrap your text into <p></p> tag while rendering the page, if the <p> tag was not there in the original rtf text.

If you go for method 1: <saveRichTextContent>
You could add to the pipeline in web.config:

<processor type="Sitecore72.Classes.WrapRichTextInParagraphOnSave, Sitecore72" />

And you could use the following corresponding code:

    namespace Sitecore72.Classes
    {
        public class WrapRichTextInParagraphOnSave
        {
            public void Process(SaveRichTextContentArgs args)
            {
                if (!(args.Content.Trim().StartsWith("<p>") && args.Content.Trim().EndsWith("</p>")))
                    args.Content = "<p>" + args.Content + "</p>";
            }
        }
    }

Please note, that this pipeline gets triggered only when you use the Show Editor buttong of a rich text field:
2014-06-14_215418

If you go for method 2: <renderField>

To append to this pipeline you would use this config:

<processor type="Sitecore72.Classes.WrapRichTextInParagraphOnRender, Sitecore72" />

And you could use the following corresponding code:

    namespace Sitecore72.Classes
    {
        public class WrapRichTextInParagraphOnRender
        {
            public void Process(RenderFieldArgs args)
            {
                if (args.FieldTypeKey == "rich text" && !(args.Result.FirstPart.Trim().StartsWith("<p>") && args.Result.FirstPart.Trim().EndsWith("</p>")))
                    args.Result.FirstPart = "<p>" + args.Result.FirstPart + "<//p>";
            }
        }
    }

For both these, ensure you add reference to Sitecore.Kernel.dll.

, , , , , , ,

3 Comments