Feeds:
Posts
Comments

Archive for the ‘C#’ Category

The documentation for Sitefinity is a bit… hit or miss. Some of the newer features, such as the Fluent API, simply do not have enough examples / documentation available to be able to work out of the gate. Such was the case with my task for today: adding a custom module’s widgets into the Siteifinty install’s toolbox.

First of all, create your Resource class. I created this a the root of my project, in the same folder as the ContentModuleBase class. This class will contain, among other things, all of the friendly names and descriptions that your widget will use:

using Telerik.Sitefinity.Localization;
using Telerik.Sitefinity.Localization.Data;

namespace me.Sitefinity.BusinessOpportunities
{
    /// <summary>
    /// Resource class for the procuts module
    /// </summary>
    [ObjectInfo(typeof(BusinessOpportunitiesResources), Title = "BusinessOpportunitiesResourcesTitle", Description = "BusinessOpportunitiesResourcesDescription")]
    public class BusinessOpportunitiesResources : Resource
    {
                #region Constructors
        
        /// <summary>
        /// Initializes new instance of  class with the default .
        /// </summary>
        public BusinessOpportunitiesResources()
        {
        }

        /// <summary>
        /// Initializes new instance of  class with the provided .
        /// </summary>
        /// 
        public BusinessOpportunitiesResources(ResourceDataProvider dataProvider)
            : base(dataProvider)
        {
        }

        #endregion

        #region Class Description

        /// <summary>
        /// The title of this class
        /// </summary>
        [ResourceEntry("BusinessOpportunitiesResourcesTitle",
            Value = "Business Opportunities",
            Description = "The title of this class.",
            LastModified = "2012/12/07")]
        public string BusinessOpportunitiesResourcesTitle
        {
            get { return this["BusinessOpportunitiesResourcesTitle"]; }
        }

        /// <summary>
        /// The description of this class
        /// </summary>
        [ResourceEntry("BusinessOpportunitiesResourcesDescription",
            Value = "Contains localizable resources for Business Opportunities module.",
            Description = "The description of this class.",
            LastModified = "2012/12/07")]
        public string BusinessOpportunitiesResourcesDescription
        {
            get { return this["BusinessOpportunitiesResourcesDescription"]; }
        }

        #endregion

        #region toolbox resources

        [ResourceEntry("BusinessOpportunitiesToolboxTitle",
            Value = "Bidness",
            Description = "Title of the toolbox entry",
            LastModified = "2012/12/07")]
        public string BusinessOpportunitiesToolboxTitle
        {
            get { return this["BusinessOpportunitiesToolboxTitle"]; }
        }

        [ResourceEntry("BusinessOpportunitiesToolboxDescription",
            Value = "Contains localizable resources for Business Opportunities module.",
            Description = "Description of the toolbox entry",
            LastModified = "2012/12/07")]
        public string BusinessOpportunitiesToolboxDescription
        {
            get { return this["BusinessOpportunitiesToolboxDescription"]; }
        }

        #endregion toolbox resources

        #region widget resources

        /// <summary>
        /// phrase: Widget that displays real estate items
        /// </summary>
        [ResourceEntry("MasterListViewTitle",
            Value = "Opportunities",
            Description = "phrase: Widget that displays real estate items",
            LastModified = "2012/12/07")]
        public string MasterListViewTitle
        {
            get { return this["MasterListViewTitle"]; }
        }

        
        /// <summary>
        /// phrase: Widget that displays real estate items
        /// </summary>
        [ResourceEntry("MasterListViewDescription",
            Value = "Widget that displays real estate items",
            Description = "phrase: Widget that displays real estate items",
            LastModified = "2012/12/07")]
        public string MasterListViewDescription
        {
            get { return this["MasterListViewDescription"]; }
        }

        #endregion widget resources
    }
}

Now you will be able to reference these friendly strings when you integrate your widget into the toolbox.

Next you have to make sure that this resources file is installed into Sitefinity by your custom module. In your ContentModuleBase class, in your Initialize method, make sure to add it in as follows:

public override void Initialize(ModuleSettings settings)
{
	base.Initialize(settings);

	// initialize configuration file
	App.WorkWith()
		.Module(settings.Name)
		.Initialize()
			.Configuration<BusinessOpportunitiesConfig>()
			.Localization<BusinessOpportunitiesResources>()
			.WebService<BusinessOpportunitiesBackendService>("Sitefinity/Services/Content/BusinessOpportunities.svc");
}

The key line is the .Localization. This is the same as:

Res.RegisterResource();

in the pre-fluent API implementation.

And then finally, in the InstallConfiguration method, you need to add your section and widgets, ensuring that you reference the localization settings (“Resources”) along the way:

/// <summary>
/// Installs module's toolbox configuration.
/// </summary>
/// <param name="initializer">The initializer.</param>
protected override void InstallConfiguration(SiteInitializer initializer)
{
	// Module widget is installed on Bootstrapper_Initialized
	initializer.Installer
		.PageToolbox()
		
			.LoadOrAddSection("MySection2")
				.LocalizeUsing<BusinessOpportunitiesResources>()
				.SetTitle("BusinessOpportunitiesToolboxTitle")
				.SetDescription("BusinessOpportunitiesToolboxDescription")

				.LoadOrAddWidget<MasterListView>("MasterListView")
					.LocalizeUsing<BusinessOpportunitiesResources>()
					.SetTitle("MasterListViewTitle")
					.SetDescription("MasterListViewDescription")
					.Done()

				.Done()
			.Done();
}

With that, you should end up with entries in your toolbox like the following:

Custom widget in Sitefinity Toolbox

Custom widget in Sitefinity Toolbox

Advertisements

Read Full Post »

In a recent project, one of my models had the following validation requirements:
If “CompletedDate” has a value (a date value), “Status” must be set to “Completed”. If “Status” is set to “Completed”, the “CompletedDate” must be set.

In other words, validation wasn’t simply that a value must be a string, or a number between x and y, etc; the validation of one field is dependant on the value in the other and vice versa.

I first went down this route, running the validation in the data layer (in the Entity Framework stuff):
MSDN: How to: Customize Data Field Validation in the Data Model

This was pretty good, but the problem is *when* the validation occurs when using this approach: when the model is being saved. I want validation to run *before* saving the data, so that MVC can know that there’s a problem, and send some friendly message to the user. A typical “edit” command in a controller looks like this:

[HttpPost]
public ActionResult Edit(ProjectTask projecttask)
{
	if (ModelState.IsValid)
	{
		db.ProjectTasks.Attach(projecttask);
		db.ObjectStateManager.ChangeObjectState(projecttask, EntityState.Modified);
		db.SaveChanges();
		return RedirectToAction("Details", "Project", new { id = projecttask.Workorder.CallID });
	}
	return View(projecttask);
}

So I *do* have an option, I could put a try around db.SaveChanges, and catch my validation error there. However, I would then have to grab the error message and figure out some way to send that back to the client (via ViewBag, most likely). I didn’t like this.

I then inspected this ModelState.IsValid method, and had the thought to create an extension method along the lines of ModelState.IsValidProjectTask(). With this, I can add error messages into the ModelState, specific to the fields. This means Razor script like:

@Html.ValidationMessageFor(model => model.CompletionDate)

will work, returning a proper error message where appropriate.

So here’s what I did:

[HttpPost]
public ActionResult Edit(ProjectTask projecttask)
{
	if (ModelState.IsValidProjectTask())
	{
		db.ProjectTasks.Attach(projecttask);
		db.ObjectStateManager.ChangeObjectState(projecttask, EntityState.Modified);
		db.SaveChanges();
		return RedirectToAction("Details", "Project", new { id = projecttask.Workorder.CallID });
	}
	return View(projecttask);
}

and then in my Models namespace:

using System.Web.Mvc;

namespace MyProject.Models
{
    public static class ModelStateDictionaryExtensions
    {
        public static bool IsValidProjectTask(this ModelStateDictionary modelStateDictionary)
        {
            return modelStateDictionary.IsValid & ValidateDates(modelStateDictionary);
        }

        private static bool ValidateDates(ModelStateDictionary modelStateDictionary)
        {
            ModelState outState;
            var submittedDate = string.Empty;
            var submittedStatus = string.Empty;

            if (modelStateDictionary.ContainsKey("CompletionDate"))
            {
                modelStateDictionary.TryGetValue("CompletionDate", out outState);
                submittedDate = outState.Value.AttemptedValue;
            }
            if (modelStateDictionary.ContainsKey("Status"))
            {
                modelStateDictionary.TryGetValue("Status", out outState);
                submittedStatus = outState.Value.AttemptedValue;
            }

            if (submittedStatus.ToUpper() == "COMPLETED" & string.IsNullOrEmpty(submittedDate))
            {
                modelStateDictionary.AddModelError("CompletionDate", "Task completed, but no completion date provided.");
                return false;
            }
            if (submittedStatus.ToUpper() != "COMPLETED" & !string.IsNullOrEmpty(submittedDate))
            {
                modelStateDictionary.AddModelError("Status", "Completion date provided, but task not completed.");
                return false;
            }

            return true;
        }
    }
}

Read Full Post »