Visual Studio 2008 Beta 2 Virtual PC (VPC) images to expire on November 1st

Most of us have been testing the Visual Studio 2008 Beta 2 VPC images.

Microsoft has announced that the current Visual Studio 2008 Beta 2 VPC images will expire on November 1, 2007, rather than March 15, 2008 as originally announced.

It is strongly encouraged that you take all necessary steps before November 1, 2007 to back up all your projects and move your Team Foundation Server data to an alternate location. For instructions on moving your TFS data please refer to the article Moving Team Foundation Server,

For the latest information and up to date information on this please refer to the Visual Studio Developer Center.

A short Frequently Asked Questions (FAQ) is listed below:

Q. Will my data be available after November 1, 2007?

A. This is still being researched, however, currently the understanding is that customers will NOT be able to access their date after November 1st unless the data is moved to an alternate installation location.

Q. Can I reset my system date to re-enable the OS image?

A. Again there is still research being done, however, from the current understanding of the problem resetting the system date back DOES NOT re-enable the OS image.

If I have any further information to share will be sure to let you know.

Are Page Modules Still Useful In IIS7?

With IIS7 a new transfer method is available in the HttpServerUtility class. It’s the TransferRequest method.

What this method is intended to do is behave like the HttpResponse.Redirect method without the penalty of traveling to the client and back.

I said “is intended to behave” because it still doesn’t, like Luís Abreu found out in his first attempt to use this new method. Fortunately, Thomas Marquardt already knows about it and said they will fix it.

This problem that Luís ran into reminds us that there is still too much going on when you call TransferRequest that won’t be if a Page Module is used. So, I guess they are still useful.

Improving The Page Flow Application Block: Removing Database Dependencies

Introduction


Especially in development and demonstration scenarios, the dependency on a database can be a big hassle. In this article I’ll show how to remove this dependency.


Instance Correlation Provider


A Page Flow Application Block‘s instance correlation provider is responsible for maintaining information about page flow instances like instance id, type and running status. These providers must implement the Microsoft.Practices.PageFlow.IPageFlowInstanceStore:


namespace Microsoft.Practices.PageFlow
{
    /// <summary>
    /// Defines the interface for an object that can store <see cref=”IPageFlow”/>s.
    /// </summary>
    public interface IPageFlowInstanceStore
    {
        /// <summary>
        /// Adds an <see cref=”IPageFlow”/> to the store.
        /// </summary>
        /// <param name=”pageFlowInstance”>The <see cref=”IPageFlow”/> to add.</param>
        void Add(IPageFlow pageFlowInstance);

        /// <summary>
        /// Removes an <see cref=”IPageFlow”/> from the store.
        /// </summary>
        /// <param name=”pageFlowInstance”>The <see cref=”IPageFlow”/> to remove.</param>
        void Remove(IPageFlow pageFlowInstance);

        /// <summary>
        /// Removed an <see cref=”IPageFlow”/> from the store.
        /// </summary>
        /// <param name=”id”>The unique identifier of the <see cref=”IPageFlow”/> to remove from the store.</param>
        void Remove(Guid id);

        /// <summary>
        /// Returns the <see cref=”Guid”>Guid</see> of the last instance.
        /// </summary>
        /// <returns>The unique identifier of the PageFlow instance.</returns>
        Guid GetLastRunningInstance();

        /// <summary>
        /// Retrieves the unique identifier of an <see cref=”IPageFlow”>IPageFlow</see> instance 
        /// of the desired <see cref=”Type”>Type</see>.
        /// </summary>
        /// <param name=”type”>The <see cref=”Type”/> of <see cref=”IPageFlow”/> to retrieve.</param>
        /// <returns>The unique idenitifier of an instance of the desired <see cref=”Type”/>.</returns>
        Guid GetByType(Type type);

        /// <summary>
        /// Retrieves the instance <see cref=”Type”>Type</see> assembly qualified full name correspnding to an instance id
        /// </summary>
        /// <param name=”id”>The <see cref=”Guid”>Guid</see> of the instance</param>
        /// <returns>The assembly qualified name of the <see cref=”Type”>Type</see> of the instance or null if not found</returns>
        string GetInstanceType(Guid id);

        /// <summary>
        /// Marks an <see cref=”IPageFlow”>IPageFlow</see> instance as the currently running instance.
        /// </summary>
        /// <param name=”iPageFlow”>The <see cref=”IPageFlow”>IPageFlow</see> instance to mark as running.</param>
        void SetPageFlowRunning(IPageFlow iPageFlow);

        /// <summary>
        /// Marks an <see cref=”IPageFlow”>IPageFlow</see> instance as not currently running.
        /// </summary>
        /// <param name=”iPageFlow”>The <see cref=”IPageFlow”>IPageFlow</see> instance to mark as not currently running.</param>
        void SetPageFlowNotRunning(IPageFlow iPageFlow);
    }
}

The provided implementation uses an SQL Server database to store the page flow instance data.


To remove this dependency on a database I’ll have to build a new page flow instance store.


If the sole purpose of this provider was to be only used in development, some in-memory dictionary would be just fine. But I want to build something that’s as much as possible alike the provided provider and of production quality.


For me, the best choice was to use the ASP.NET session state. This way I keep instances isolated from session to session (this is different from the provided provider where page flow instances can be used across ASP.NET sessions) and can, ultimately, be stored in a data base for persistence and load balancing.


Implementing the PageFlowInstanceCorrelationAspNetSessionStateProvider


To replace the database based implementation all I need is to replace the database.


Since the provided implementation only uses one table, this means that it only has one entity – a storage item.


Storage Item


This is the entity that stores the information about page flow instances and its fields have a direct correspondence to the columns of the table of the provided implementation.


[Serializable]
private class StorageItem
{
    public Guid InstanceId;
    public string PageFlowType;
    public string CorrelationToken;
    public bool Running;
}

The class is marked with the Serializable attribute to allow its instances to be serialized to and back from a persistence store like a database.


Storage


This entity represents the database table and stored procedures used to manipulate the data.


[Serializable]
private partial class Storage : System.Runtime.Serialization.ISerializable
{
    private SortedList<Guid, StorageItem> items = new SortedList<Guid, StorageItem>();

    /// <summary>
    /// Initializes a new instance of the <see cref=”T:PauloMorgado.Practices.PageFlow.Storage.AspNetSessionState.Storage”/> class.
    /// </summary>
    public Storage()
    {
    }

    #region ISerializable Members

    /// <summary>
    /// Initializes a new instance of the <see cref=”Storage”/> class.
    /// </summary>
    /// <param name=”info”>The info.</param>
    /// <param name=”context”>The context.</param>
    protected Storage(SerializationInfo info, StreamingContext context)
    {
        this.items = (SortedList<Guid, StorageItem>)info.GetValue(“items”, typeof(SortedList<Guid, StorageItem>));
    }

    /// <summary>
    /// Populates a <see cref=”T:System.Runtime.Serialization.SerializationInfo”></see> with the data needed to serialize the target object.
    /// </summary>
    /// <param name=”info”>The <see cref=”T:System.Runtime.Serialization.SerializationInfo”></see> to populate with data.</param>
    /// <param name=”context”>The destination (see <see cref=”T:System.Runtime.Serialization.StreamingContext”></see>) for this serialization.</param>
    /// <exception cref=”T:System.Security.SecurityException”>The caller does not have the required permission. </exception>
    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue(“items”, this.items);
    }

    #endregion

    internal void InsertInstance(Guid instanceId, string pageFlowType, string correlationToken, bool running)
    {
        StorageItem item = new StorageItem();
        item.InstanceId = instanceId;
        item.PageFlowType = pageFlowType;
        item.CorrelationToken = correlationToken;
        item.Running = running;

        this.items.Add(instanceId, item);
    }

    internal void DeleteInstance(Guid instanceId)
    {
        this.items.Remove(instanceId);
    }

    internal Guid GetLastRunningInstanceByCorrelationToken(string correlationToken)
    {
        foreach (StorageItem item in this.items.Values)
        {
            if (item.Running && (item.CorrelationToken == correlationToken))
            {
                return item.InstanceId;
            }
        }
        return Guid.Empty;
    }

    internal Guid GetInstanceByTypeAndByCorrelationToken(string correlationToken, string pageFlowType)
    {
        foreach (StorageItem item in this.items.Values)
        {
            if ((item.CorrelationToken == correlationToken) && (item.PageFlowType == pageFlowType))
            {
                return item.InstanceId;
            }
        }
        return Guid.Empty;
    }

    internal string GetTypeByInstanceId(Guid instanceId)
    {
        StorageItem item;
        if (this.items.TryGetValue(instanceId, out item))
        {
            return item.PageFlowType;
        }
        return null;
    }

    internal void SetRunningInstanceForCorrelationToken(Guid instanceId, string correlationToken)
    {
        foreach (StorageItem item in this.items.Values)
        {
            // set running instances to not running
            if ((item.CorrelationToken == correlationToken) && (item.Running))
            {
                item.Running = false;
            }
            // set the requested instance to running
            if ((item.InstanceId == instanceId) && (!item.Running))
            {
                item.Running = true;
            }
        }
    }

    internal void ChangeInstanceStatus(Guid instanceId, bool running)
    {
        StorageItem item;
        if (this.items.TryGetValue(instanceId, out item))
        {
            item.Running = running;
        }
    }
}

Since this is the entity being directly stored in the session state, the class is marked with the Serializable attribute to allow its instances to be serialized to an back from a persistence store like a database.


Page Flow Instance Correlation Provider


This class looks very much like the provided PageFlowInstanceCorrelationSqlProvider where the database is replaced with an instance of the Storage class and the calls to database stored procedures are replaced by calls to methods of the Storage class.


/// <summary>
/// An implementation of an <see cref=”IPageFlowInstanceStore”>IPageFlowInstanceStore</see>
/// that uses a the Enterprise Library Data Block <see cref=”Database”>Database</see> to manage 
/// and store <see cref=”IPageFlow”>IPageFlow</see> instances.
/// </summary>
public partial class PageFlowInstanceCorrelationAspNetSessionStateProvider : IPageFlowInstanceStore
{
    private string pageFlowStorageName;
    private Storage pageFlowStorage;
    private IPageFlowCorrelationTokenProvider tokenProvider;

    /// <overloads>
    /// Creates an instance of PageFlowInstanceCorrelationSqlProvider.
    /// </overloads>
    /// <summary>
    /// Creates an instance of PageFlowInstanceCorrelationSqlProvider using
    /// the provided database name.
    /// </summary>
    /// <remarks>
    /// The PageFlowInstanceCorrelationSqlProvider will use a 
    /// <see cref=”CookiePageFlowCorrelationTokenProvider”>CookiePageFlowCorrelationTokenProvider</see>
    /// to create and provide tokens for each instance.
    /// </remarks>
    /// <param name=”databaseName”>The name of the <see cref=”P:HttpContext.Session”/> variable to use as the store.</param>
    public PageFlowInstanceCorrelationAspNetSessionStateProvider(string databaseName)
        : this(databaseName, new CookiePageFlowCorrelationTokenProvider())
    {
    }

    /// <summary>
    /// Creates an instance of PageFlowInstanceCorrelationSqlProvider.
    /// </summary>
    /// <param name=”databaseName”>The name of the <see cref=”P:HttpContext.Session”/> variable to use as the store.</param>
    /// <param name=”tokenProvider”>The <see cref=”IPageFlowCorrelationTokenProvider”>IPageFlowCorrelationTokenProvider</see>
    /// implementation that will provide tokens.
    /// </param>
    public PageFlowInstanceCorrelationAspNetSessionStateProvider(string databaseName, IPageFlowCorrelationTokenProvider tokenProvider)
    {
        Guard.ArgumentNotNullOrEmptyString(databaseName, “databaseName”);
        Guard.ArgumentNotNull(tokenProvider, “tokenProvider”);
        this.pageFlowStorageName = databaseName;
        this.tokenProvider = tokenProvider;
    }

    /// <summary>
    /// Adds an <see cref=”IPageFlow”>IPageFlow</see> instance to the store.
    /// </summary>
    /// <param name=”pageFlowInstance”>The instance to add.</param>
    public void Add(IPageFlow pageFlowInstance)
    {
        Guard.ArgumentNotNull(pageFlowInstance, “pageFlowInstance”);
        string token = this.tokenProvider.GetCorrelationToken();
        this.PageFlowStorage.InsertInstance(pageFlowInstance.Id, pageFlowInstance.Definition.PageFlowType.AssemblyQualifiedName, token, false);
    }

    /// <overloads>
    /// Removes a PageFlow instance from the store.
    /// </overloads>
    /// <summary>
    /// Removes an <see cref=”IPageFlow”>IPageFlow</see> instance from the store.
    /// </summary>
    /// <param name=”pageFlowInstance”>The instance to remove.</param>
    public void Remove(IPageFlow pageFlowInstance)
    {
        Guard.ArgumentNotNull(pageFlowInstance, “pageFlowInstance”);
        this.Remove(pageFlowInstance.Id);
    }

    /// <summary>
    /// Removes the <see cref=”IPageFlow”>IPageFlow</see> instance with the appropriate Guid from the store.
    /// </summary>
    /// <param name=”id”>The unique identifier of the instance to remove.</param>
    public void Remove(Guid id)
    {
        this.PageFlowStorage.DeleteInstance(id);
    }

    /// <summary>
    /// Returns the <see cref=”Guid”>Guid</see> of the last instance.
    /// </summary>
    /// <returns>The unique identifier of the PageFlow instance.</returns>
    public Guid GetLastRunningInstance()
    {
        string token = this.tokenProvider.GetCorrelationToken();
        return this.PageFlowStorage.GetLastRunningInstanceByCorrelationToken(token);
    }

    /// <summary>
    /// Retrieves the unique identifier of an <see cref=”IPageFlow”>IPageFlow</see> instance 
    /// of the desired <see cref=”Type”>Type</see>.
    /// </summary>
    /// <param name=”type”>The <see cref=”Type”>Type</see> of PageFlow to retrieve.</param>
    /// <returns>The unique idenitifier of an instance of the desired type.</returns>
    public Guid GetByType(Type type)
    {
        Guard.ArgumentNotNull(type, “type”);

        string token = this.tokenProvider.GetCorrelationToken();
        return this.PageFlowStorage.GetInstanceByTypeAndByCorrelationToken(token, type.AssemblyQualifiedName);
    }

    /// <summary>
    /// Retrieves the instance <see cref=”Type”>Type</see> assembly qualified full name correspnding to an instance id
    /// </summary>
    /// <param name=”id”>The <see cref=”Guid”>Guid</see> of the instance</param>
    /// <returns>The assembly qualified name of the <see cref=”Type”>Type</see> of the instance or null if not found</returns>
    public string GetInstanceType(Guid id)
    {
        PauloMorgado.Practices.PageFlow.Storage.AspNetSessionState.Utils.Guard.ArgumentNotEmptyGuid(id, “id”);

        return this.PageFlowStorage.GetTypeByInstanceId(id);
    }

    /// <summary>
    /// Marks an <see cref=”IPageFlow”>IPageFlow</see> instance as the currently running instance.
    /// </summary>
    /// <param name=”pageFlowInstance”>The <see cref=”IPageFlow”>IPageFlow</see> instance to mark as running.</param>
    public void SetPageFlowRunning(IPageFlow pageFlowInstance)
    {
        Guard.ArgumentNotNull(pageFlowInstance, “pageFlowInstance”);
        string token = this.tokenProvider.GetCorrelationToken();
        this.PageFlowStorage.SetRunningInstanceForCorrelationToken(pageFlowInstance.Id, token);
    }

    /// <summary>
    /// Marks an <see cref=”IPageFlow”>IPageFlow</see> instance as not currently running.
    /// </summary>
    /// <param name=”pageFlowInstance”>The <see cref=”IPageFlow”>IPageFlow</see> instance to mark as not currently running.</param>
    public void SetPageFlowNotRunning(IPageFlow pageFlowInstance)
    {
        Guard.ArgumentNotNull(pageFlowInstance, “pageFlowInstance”);
        this.PageFlowStorage.ChangeInstanceStatus(pageFlowInstance.Id, false);
    }

    private Storage PageFlowStorage
    {
        get
        {
            Storage pageFlowStorage= HttpContext.Current.Session[this.pageFlowStorageName] as Storage;
            if (pageFlowStorage == null)
            {
                HttpContext.Current.Session[this.pageFlowStorageName] = pageFlowStorage = new Storage();
            }
            return pageFlowStorage;
        }
    }
}

Workflow Foundation


The implementation of the page flow engine provided with the Page Flow Application Block is based on Windows Workflow Foundation and assumes that a persistence service is present and calls Unload method of the workflow instance.


So far, the only persistence service supplied with the .NET framework is the SQL persistence service, which is based on SQL Server.


I could have created my own in memory persistence service but, instead, I chose to check if a persistence service is present before calling the Unload method. This was accomplished by replacing:


_instance.Unload();

with:


System.Collections.ObjectModel.ReadOnlyCollection<WorkflowPersistenceService> persistenceServices = _instance.WorkflowRuntime.GetAllServices<WorkflowPersistenceService>();
if ((persistenceServices != null) && (persistenceServices.Count != 0))
{
    _instance.Unload();
}

in the Microsoft.Practices.PageFlow.WorkflowFoundation.WorkflowFoundationPageFlow.Suspend and PageFlow.WorkflowFoundation.WorkflowFoundationPageFlowFactory.GetPageFlow methods.


Page Flow Store QuickStart


(This sample supplied with the Web Client Software Factory will be used to test and demonstrate the improvements made to the Page Flow Application Block.)


Having built a database-free page flow application block, all I need now is to configure the application to use the new implementations by editing the web.config file and replacing:


<?xml version=1.0?>
<configuration xmlns=http://schemas.microsoft.com/.NetConfiguration/v2.0>
  <connectionStrings>
    <add name=PageFlowPersistanceStore connectionString=Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=WCSF_Quickstart;Data Source=.\SQLExpress providerName=System.Data.SqlClient/>
  </connectionStrings>
  <pageFlow>
    <pageFlowProvider providerType=Microsoft.Practices.PageFlow.WorkflowFoundation.WorkflowFoundationPageFlowProvider, Microsoft.Practices.PageFlow.WorkflowFoundation/>
    <hostingWorkflowRuntime Name=Hosting>
      <Services>
        <add type=System.Workflow.Runtime.Hosting.SqlWorkflowPersistenceService, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 connectionString=Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=WCSF_Quickstart;Data Source=.\SQLEXPRESS; LoadIntervalSeconds=5 UnloadOnIdle=true/>
        <add type=System.Workflow.Runtime.Hosting.ManualWorkflowSchedulerService, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 useActiveTimers=true/>
        <add type=System.Workflow.Activities.ExternalDataExchangeService, System.Workflow.Activities, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35/>
      </Services>
    </hostingWorkflowRuntime>
    <pageFlowInstanceStoreProvider providerType=Microsoft.Practices.PageFlow.Storage.EnterpriseLibrary.PageFlowInstanceCorrelationSqlProvider, Microsoft.Practices.PageFlow.Storage.EnterpriseLibrary connectionString=PageFlowPersistanceStore/>
    <pageFlowInstanceCorrelationTokenProvider providerType=Microsoft.Practices.PageFlow.Storage.EnterpriseLibrary.CookiePageFlowCorrelationTokenProvider, Microsoft.Practices.PageFlow.Storage.EnterpriseLibrary />
  </pageFlow>
</configuration>

with:


<?xml version=1.0?>
<configuration xmlns=http://schemas.microsoft.com/.NetConfiguration/v2.0>
  <connectionStrings>
  </connectionStrings>
  <pageFlow>
    <pageFlowProvider providerType=Microsoft.Practices.PageFlow.WorkflowFoundation.WorkflowFoundationPageFlowProvider, Microsoft.Practices.PageFlow.WorkflowFoundation/>
    <hostingWorkflowRuntime Name=Hosting>
      <Services>
        <add type=System.Workflow.Runtime.Hosting.ManualWorkflowSchedulerService, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 useActiveTimers=true/>
        <add type=System.Workflow.Activities.ExternalDataExchangeService, System.Workflow.Activities, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35/>
      </Services>
    </hostingWorkflowRuntime>
    <pageFlowInstanceStoreProvider providerType=PauloMorgado.Practices.PageFlow.Storage.AspNetSessionState.PageFlowInstanceCorrelationAspNetSessionStateProvider, PauloMorgado.Practices.PageFlow.Storage.AspNetSessionState connectionString=PageFlowPersistanceStore/>
    <pageFlowInstanceCorrelationTokenProvider providerType=Microsoft.Practices.PageFlow.Storage.EnterpriseLibrary.CookiePageFlowCorrelationTokenProvider, PauloMorgado.Practices.PageFlow.Storage.AspNetSessionState />
  </pageFlow>
</configuration>

But the sample application still has a dependency to a membership provider that uses SQL Server.


To remove this dependency, I’ll use the Login control with the Forms Authentication’s user definitions and I’ll need to edit the login page and web.config file:


<%@ Page MasterPageFile=”~/Shared/QuickStarts.master” Title=”Page Flow Store QuickStart – Login” Language=”C#” AutoEventWireup=”true” CodeFile=”Login.aspx.cs” Inherits=”Login” %>

<asp:Content ID=”Content1″ ContentPlaceHolderID=”mainContent” runat=”Server”>
    <h2>Login</h2>
    <div>
        <p>Enter “user” for the user name and “p@ssw0rd” for the password.</p>
        <asp:Login ID=”Login1″ runat=”server” OnAuthenticate=”Login1_Authenticate”>
        </asp:Login>
    </div>
</asp:Content>

public partial class Login : Page
{
    protected void Login1_Authenticate(object sender, System.Web.UI.WebControls.AuthenticateEventArgs e)
    {
        e.Authenticated = FormsAuthentication.Authenticate(Login1.UserName, Login1.Password);
    }
}

<?xml version=1.0?>
<configuration xmlns=http://schemas.microsoft.com/.NetConfiguration/v2.0>
  <connectionStrings>
    <!– removed – <add name=”MembershipStore” connectionString=”Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=WCSF_Quickstart;Data Source=.\SQLExpress” providerName=”System.Data.SqlClient”/>–>
  </connectionStrings>
  <system.web>

    <authentication mode=Forms>
      <!– added start –>
      <forms>
        <credentials passwordFormat=Clear>
          <user name=user password=p@ssw0rd/>
        </credentials>
      </forms>
      <!– added end –>
    </authentication>

    <!– removed – <membership defaultProvider=”SqlProvider”>
      <providers>
        <clear />
        <add
          name=”SqlProvider”
          type=”System.Web.Security.SqlMembershipProvider”
          connectionStringName=”MembershipStore”
          applicationName=”WCSF_Quickstart”
          passwordFormat=”Hashed” />
      </providers>
    </membership>–>
</configuration>

Conclusion


And I’m all set to go and develop (or even go to production) without the need for a database.


Resources


Scott Guthrie Announces MVP Framework For ASP.NET

I usually just share these news through my shared readings feed, but this one is too big the let it pass without saying anything.

Glenn Block just pointed me to his post on this.

Jeffrey Palermo also has a great post about the new MVP Framework For ASP.NET.

I’ll wait to see what’s coming out of that. I like extensibility, dependency injection, inversion of control all that good stuff that’s missing from ASP.NET (adding a few providers doesn’t make ASP.NET a framework with extensibility, dependency injection, inversion of control, etc.).

The fact that both the “new” and the “old” model can live together in the same web application means that nothing has been done to make better ASP.NET web applications. It’s just another way to glue some pages together.

What I would like to see is, for example:

  • Pluggable authentication providers
  • Pluggable cookieless providers

But, let’s see what comes out of that.

VS REGEX: Commenting Generated Assert Instructions

Visual Studio’s Unit Test generator generates a call to Assert.Inconclusive but, usually generates a call to another method of the Assert class. I find that very annoying because, some times, this makes the test fail instead of being reported as inconclusive.


To comment out these extra calls to Assert methods, the following regular expression can be used:


Find what:

{:b*}{Assert\.~(Inconclusive).*\n:b*Assert\.Inconclusive}

Replace with:

\1//\2

Check out the complete list.

VS REGEX: Joining Concatenated Strings

I don’t like to see constant strings being concatenated in source code. I know the compiler compiles it into a single string, but I think it sends out the wrong message.

However, code generators (like Visual Studio’s Unit Test generator) usually generate code with long strings concatenated across several lines of source code.

In order to replace this:

text = "This is a one " + "line concatenation." +
       "This is a multiline concatenation."
       + "This is a multiline concatenation."


       +



       "This is a multiline concatenation with multiple blank lines." +
       "This looks like a string concatenation \" + " + "."

into this:


text = "This is a one line concatenation.This is a multiline concatenation.This is a multiline concatenation.This is a multiline concatenation with multiple blank lines.This looks like a string concatenation \" + ."




The following regular expression can be used:


Find what:

~(\\)\”:b*(:b*\n)*\+(:b*\n)*:b*\”

Replace with:

 

There’s one caveat, though. This Regular expression only works with regular C# string literals. It doesn’t work with Visual Basic or @-quoted C# strings.


Check out the complete list.