Fixing Visual Studio Intellisense in MVC Views


Hi folks,

long time ago since my last post. Hard working all the time to see happy customers with a great experience. ūüėČ

But that post is a short one and of course as a clean developer you don’t want to have intellisense problems in your environment within MVC Views!!!

Sean Holmesby figured it out: http://www.seanholmesby.com/fixing-visual-studio-intellisense-in-sitecore-mvc-views/

But you don’t¬†need all the Configs in the App_Config folder, that’s the core of this post. Always use a less configs in your environment as you can. Sitecore upgrades become harder as more configs you have to upgrade.

So, reduce it to the max, you need:

  • the /Views/web.config
  • the /App_Config/Sitecore.config
  • the /web.config

… in your Visual Studio Project.

When you include the /web.config in your project you will see some more errors because some dll’s are not found. I always reference¬†all this missing dll’s in my solution because I like the “zero warning policy” ūüėČ

Happy configuring

Dirk

 

Advertisements

Web Forms for Marketers (WFFM) ‚Äí Part 3 ‚Äí Multipart Form, Custom Save Action and a Summary Page with a Custom Validator


Put it all together!

The Registration form consists of three steps. In the first step you can choose a seminar and participants. In the second step you have to fill out address details and at least the third step is a tricky summary page with a custom Summary field which inherits from ListControl, displaying the title of the seminar, all participants and the final price for all the persons.

Step 1

Step 1

Step 2

Step 2

Step 3

Step 3

Now, we know how the form looks like. But there are a lot of things we should take a closer look! ūüėČ

The topmost question is: How to do a multi step form on a single page? There are a few examples in the web where each step has a success page (the next step) Multistep forms using WFFM (webforms for marketeers)

That wasn’t a solution for me, because we have to stay on the current page, there are several seminars on the page in different categories. We can’t expect from the customer to create three success pages for each seminar… that would be hundreds of pages!

Much better!! We create a custom SuccessRedirect-Pipeline and patched it before the Sitecore.Form.Core.Pipelines.FormSubmit.SuccessRedirect-Pipeline.

Configuration

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
    <sitecore>
        <pipelines>
            <processMessage>
                <processor type="Project.Infrastructure.Pipelines.Form.ProcessSeminarMessage, Project.Infrastructure" patch:before="processor[@method='SendEmail']"/>
            </processMessage>
            <successAction>
                <processor type="Project.Infrastructure.Pipelines.SuccessRedirect, Project.Infrastructure" patch:before="processor[@type='Sitecore.Form.Core.Pipelines.FormSubmit.SuccessRedirect, Sitecore.Forms.Core']"/>
            </successAction>
        </pipelines>
    </sitecore>
</configuration>

 

SuccessRedirect-Pipeline

public class SuccessRedirect : ClientPipelineArgs
{
    private const string RegistrationFormularAnchor = "#SeminarRegistrationContainer";

    public void Process(SubmitSuccessArgs args)
    {
        Assert.IsNotNull(args, "args");
        if (args.Form == null)
        {
            return;
        }
        if (args.Form.ID != ItemIds.Forms.SeminarRegistrationStep1 && args.Form.ID != ItemIds.Forms.SeminarRegistrationStep2)
        {
            return;
        }
        var url = GetRedirectUrlForRegistrationFormular(args);
        if (string.IsNullOrEmpty(url))
        {
            return;
        }
        args.AbortPipeline();
        WebUtil.Redirect(url, true);
    }

    private string GetRedirectUrlForRegistrationFormular(SubmitSuccessArgs args)
    {
        var url = LinkManager.GetItemUrl(Context.Item);

        WebUtil.RemoveQueryString(url);

        var parameter = new[] {"step", "2"};
        if (args.Form.ID == ItemIds.Forms.SeminarRegistrationStep1)
        {
            return WebUtil.AddQueryString(url, parameter) + RegistrationFormularAnchor;
        }
        if (args.Form.ID == ItemIds.Forms.SeminarRegistrationStep2)
        {
            parameter = new[] {"step", "3"};
            return WebUtil.AddQueryString(url, parameter) + RegistrationFormularAnchor;
        }
        return url;
    }
 }

The code is quite simple! All three Forms are added in standard values as a rendering. I check the form-id which is submitted and add a parameter to the url like ?step=2. On the Seminar Form  Page I simply have to check the parameter in the url and display the appropriate form.

Such easy is multistep form redirection!!! ūüėÄ

Summary Field

All fields which are neccessary for the summary field and the final email-message are tagged:

Tag

In this case we are able to get all Submits (form-steps) from the current Analytics Session and we can use them to show a summary with informations submitted in Step 1. I displayed the chosen Seminar Title included the Seminar Appointment (date and location) and all applicants, also submitted in form step 1.

Code looks like this:

[ValidationProperty("Value")]
public class SeminarFormSummary : ListControl
{
    private const string baseCssClassName = "scfSummaryListBorder";
    private AppointmentModel _appointment;
    private SeminarModel _seminar;
    private IField _seminarRadioListField;
    private string _validationValue;
    protected RadioButtonList bulletedList = new RadioButtonList();

    public SeminarFormSummary(HtmlTextWriterTag tag) : base(tag)
    {
        CssClass = baseCssClassName;
        Inject();
    }

    public SeminarFormSummary() : this(HtmlTextWriterTag.Div)
    {
        Inject();
    }

    [Inject]
    public IFormService FormService { get; set; }

    [Inject]
    public ISeminarSummaryControlFactory SeminarSummaryControlFactory { get; set; }

    public override string ID
    {
        get { return base.ID; }
        set
        {
            title.ID = value + "text";
            bulletedList.ID = value + "scope";
            base.ID = value;
        }
    }

    public override ControlResult Result
    {
        get
        {
           return new ControlResult(ControlName,  _validationValue ?? string.Empty,  string.Empty);
        }
    }

    protected override System.Web.UI.WebControls.ListControl InnerListControl
    {
        get { return bulletedList; }
    }

    [DefaultValue("scfSummaryListBorder")]
    [VisualProperty("Css Class:", 600)]
    [VisualFieldType(typeof (CssClassField))]
    public new string CssClass
    {
        get { return base.CssClass; }
        set { base.CssClass = value; }
    }

    public string Value
    {
        get
        {
            var stringBuilder = new StringBuilder();
            foreach (var listItem in InnerListControl.Items.Cast<ListItem>().Where(listItem => listItem.Selected))
            {
                stringBuilder.AppendFormat("<item>{0}</item>", listItem.Value);
            }
            return stringBuilder.ToString();
        }
    }

    private void Inject()
    {
        var bs = new Bootstrapper();
        bs.Kernel.Inject(this);
    }

    protected override void OnInit(EventArgs e)
    {
        base.OnInit(e);
        bulletedList.CssClass = "scfSummaryList";
        help.CssClass = "scfSummaryListUsefulInfo";
        title.CssClass = "scfSummaryListLabel";
        title.Text = GetTitle();
        generalPanel.CssClass = "scfSummaryListGeneralPanel";
        Controls.AddAt(0, generalPanel);
        Controls.AddAt(0, title);
        generalPanel.Controls.AddAt(0, help);
        generalPanel.Controls.AddAt(0, bulletedList);
    }

    private string GetTitle()
    {
        var seminarTitle = string.Empty;
        if (_seminar != null)
        {
            seminarTitle += string.Format("<h2>{0}</h2>", _seminar.Title);
        }
        if (_appointment != null)
        {
            seminarTitle += string.Format("<h4>{0}, {1}</h4>",
            _appointment.DateTimeFrom.ToLongDateString(),
            _appointment.Location);
        }
        seminarTitle += string.Format("<h6>{0}</h6>", Translator.Translate("Teilnehmer"));
        return seminarTitle;
    }

    protected override void OnPreRender(EventArgs e)
    {
        base.OnPreRender(e);
        title.AssociatedControlID = null;
    }

    protected override void InitItems(ListItemCollection collection)
    {
        var formStep1 = FormService.GetSubmit(ItemIds.Forms.SeminarRegistrationStep1, AnalyticsTracker.SessionId);
        if (formStep1 != null)
        {
            _seminarRadioListField = FormService.GetField(formStep1, new Guid(FieldIds.Forms.SeminarRegistration.Seminars));
            if (_seminarRadioListField != null && !string.IsNullOrEmpty(_seminarRadioListField.Value))
            {
                GetAppointmentAndSeminar(_seminarRadioListField.Value);
            }
        }

        if (_seminar == null)
            return;

        var participantsCollection = SeminarSummaryControlFactory.CreateParticipantsListItemCollection();
        base.InitItems(participantsCollection);
    }

    private void GetAppointmentAndSeminar(string value)
    {
        var context = new SitecoreContext();
        _appointment = context.GetItem<AppointmentModel>(new Guid(value));
        _seminar = (_appointment != null) ? context.GetItem<SeminarModel>(_appointment.ParentItem.Id) : null;
        _validationValue = value;
    }
 }

 

Sitecore

Sitecore 7, MVC, JSON-Serialization and maxJsonLength


Since Sitecore 7 and MVC there is a problem with JSON-Serialization, if you use aliases and set the data source to a folder with a large amount of items (in my case approx. 2000 items).

I¬†can’t switch to item buckets at the moment, because ¬†a console application fetches products from a customer url , create items and push them to a Sitecore data storage.

If you want to add a new alias and choose a linked item, nothing happened. A short view in console reveals the problem:

Object {statusCode: 500, error: Object}

error: Object
message: "Error during serialization or deserialization using the JSON JavaScriptSerializer. The length of the string exceeds the value set on the maxJsonLength property."
__proto__: Object

statusCode: 500

Hmmmkay! Almighty Google has a lot of hits if you search for the problem, up to custom JsonValueProvider or configuration examples, but none of them worked for me. Together with Sitecore support we found a solution for that: A custom JsonSerializer, a PreprocessRequest-Pipeline and a .config-file which should be placed in App_Config/Include. Here is the code:

JsonSerializer

using System;
using System.Globalization;
using System.Web.Script.Serialization;
using Sitecore.Configuration;
using Sitecore.Diagnostics;
using Sitecore.ItemWebApi.Serialization;

namespace SitecoreContrib.Serialization
{
  public class JsonSerializer : ISerializer
  {
    public string SerializedDataMediaType
    {
      get { return "application/json"; }
    }

    public string Serialize(object value)
    {
      Assert.ArgumentNotNull(value, "value");
      var scriptSerializer = new JavaScriptSerializer { MaxJsonLength = 2097152 };
      var setting = Settings.GetSetting("JsonSerialization.MaxLength");
      int result;
      if (!string.IsNullOrEmpty(setting) && !scriptSerializer.MaxJsonLength.ToString(CultureInfo.InvariantCulture).Equals(setting, StringComparison.InvariantCultureIgnoreCase) && int.TryParse(setting, out result))
      {
        scriptSerializer.MaxJsonLength = result;
      }
      return scriptSerializer.Serialize(value);
    }
  }
}

The PreprocessRequest-Pipeline


using System;
using System.Web;
using Sitecore.Diagnostics;
using Sitecore.ItemWebApi;
using Sitecore.Pipelines.PreprocessRequest;
using Sitecore.Support.ItemWebApi.Serialization;
using Sitecore.Text;
using Sitecore.Web;

namespace SitecoreContrib.Serialization.Pipelines.PreprocessRequest
{
  public class RewriteUrl
  {
    public virtual void Process(PreprocessRequestArgs arguments)
    {
      Assert.ArgumentNotNull(arguments, "arguments");
      try
      {
        var localPath = arguments.Context.Request.Url.LocalPath;
        if (!localPath.StartsWith("/-/item/")) {
          return;
        }
        var context = new Sitecore.ItemWebApi.Context
        {
          Serializer = new JsonSerializer(),
          Version = GetVersion(localPath)
        };
        Sitecore.ItemWebApi.Context.Current = context;
        Rewrite(arguments.Context);
      }
      catch (Exception ex)
      {
        Logger.Error(ex);
      }
    }

    private static int GetVersion(string path)
    {
      Assert.ArgumentNotNull(path, "path");
      var str = path.TrimStart(new char[1] {'/'}).Split(new char[1] {'/' })[2];
      Assert.IsTrue(str.StartsWith("v"), "Version token is wrong.");
      int result;
      Assert.IsTrue(int.TryParse(str.Replace("v", string.Empty), out result), "Version not recognized.");
      return result;
    }

    private static void Rewrite(HttpContext context)
    {
      Assert.ArgumentNotNull(context, "context");
      var url = context.Request.Url;
      var strArray1 = url.LocalPath.TrimStart(new char[1] {'/'}).Split(new char[1] {'/'});
      var length = strArray1.Length - 3;
      var strArray2 = new string[length];
      Array.Copy(strArray1, 3, strArray2, 0, length);
      var str1 = string.Format("/{0}", string.Join("/", strArray2));
      var str2 = url.Query.TrimStart(new char[1] {'?'});
      WebUtil.RewriteUrl(new UrlString
      {
        Path = str1,
        Query = str2
      }.ToString());
    }
  }
}

The .config-file

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set">
  <sitecore>
    <pipelines>
      <initialize>
        <processor type="Sitecore.Pipelines.Loader.ShowVersion, Sitecore.Kernel">
          <assemblies>
            <assembly id="SitecoreContrib.Serialization">/bin/SitecoreContrib.Serialization</assembly>
          </assemblies>
        </processor>
      </initialize>
      <preprocessRequest>
        <processor type="SitecoreContrib.Serialization.Pipelines.PreprocessRequest.RewriteUrl, SitecoreContrib.Serialization" patch:instead="*[@type='Sitecore.ItemWebApi.Pipelines.PreprocessRequest.RewriteUrl, Sitecore.ItemWebApi']" />
      </preprocessRequest>
    </pipelines>
    <settings>
      <!-- JsonSerialization.MaxLength
           Specifies the maximum length of JSON strings which can be serialized by the JsonSerializer.
           Value is specified in bytes. Default value: 2097152 (2 MB)
      -->
      <setting name="JsonSerialization.MaxLength" value="2147483647" />
    </settings>
  </sitecore>
</configuration>

Gotcha! From now you can configure the maxJsonLength-property for your needs.

Happy coding!

Best regards Dirk

Sitecore

Sitecore and Bundling


A short note on Sitecore and Bundling.

If you use Bundling within your WebApplication you will maybe wondering why your Script and css files not rendered properly:

 Failed to load resource: the server responded with a status of 404 (Not Found)
 http://YourDomain/sitecore/service/notfound?item=%2fbundles%2fmodernizr&user=sitecore%5cadmin&site=YourSiteName

or

 Failed to load resource: the server responded with a status of 404 (Not Found)
 http://YourDomain/sitecore/service/notfound?item=%2fcontent%2fcss&user=sitecore%5cadmin&site=YourSiteName

Sitecore tries to resolve your Bundles as an item. But you can get rid of this with a simple setting adjustment in web.config.

<setting name="IgnoreUrlPrefixes" value="/sitecore/default.aspx|/trace.axd|/webresource.axd|/sitecore/shell/Controls/Rich Text Editor/Telerik.Web.UI.DialogHandler.aspx|/sitecore/shell/applications/content manager/telerik.web.ui.dialoghandler.aspx|/sitecore/shell/Controls/Rich Text Editor/Telerik.Web.UI.SpellCheckHandler.axd|/Telerik.Web.UI.WebResource.axd|/sitecore/admin/upgrade/|/layouts/testing" />

Here we go! Just add your bundle-urls pipe-separated to the IgnoreUrlPrefixes-value

<setting name="IgnoreUrlPrefixes" value="/sitecore/default.aspx|/trace.axd|/webresource.axd|/sitecore/shell/Controls/Rich Text Editor/Telerik.Web.UI.DialogHandler.aspx|/sitecore/shell/applications/content manager/telerik.web.ui.dialoghandler.aspx|/sitecore/shell/Controls/Rich Text Editor/Telerik.Web.UI.SpellCheckHandler.axd|/Telerik.Web.UI.WebResource.axd|/sitecore/admin/upgrade/|/layouts/testing|/bundles|/Content/css" />

Best practise:

It’s highly recommended to patch such Settings in your own Settings.config-file in App_Config/Include-Folder like this:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
   <settings>
       <!-- IGNORE URLS
       Set IgnoreUrlPrefixes to a '|' separated list of url prefixes that should not be
       regarded and processed as friendly urls (ie. forms etc.)
       -->
       <setting name="IgnoreUrlPrefixes">
         <patch:attribute name="value">/sitecore/default.aspx|/trace.axd|/webresource.axd|/sitecore/shell/Controls/Rich Text Editor/Telerik.Web.UI.DialogHandler.aspx|/sitecore/shell/applications/content manager/telerik.web.ui.dialoghandler.aspx|/sitecore/shell/Controls/Rich Text Editor/Telerik.Web.UI.SpellCheckHandler.axd|/Telerik.Web.UI.WebResource.axd|/sitecore/admin/upgrade/|/layouts/testing|/bundles|/Content/css</patch:attribute>
       </setting>
     </settings>
   </sitecore>
</configuration>