ASP.NET Formatted String Control

Back to ASP.NET Web Forms land! SharePoint includes a handy control, FormattedString, that can be used to render the contents of its child controls as specified in a format string; pretty much what String.Format does. I wanted to do the same with plain old ASP.NET Web Forms, and here’s what I came up with:

[ParseChildren(false)]

[PersistChildren(true)]

[ControlBuilder(typeof(FormattedControlControlBuilder))]

public class FormattedControl : Control

{

    public String FormatString { get; set; }

 

    protected override void RenderChildren(HtmlTextWriter writer)

    {

        if (String.IsNullOrWhiteSpace(this.FormatString) == false)

        {

            var list = new List<String>();

            var builder = new StringBuilder();

            var sw = new StringWriter(builder);

            var hw = new HtmlTextWriter(sw);

 

            foreach (var control in this.Controls.OfType<Control>())

            {

                control.RenderControl(hw);

 

                list.Add(builder.ToString());

 

                builder.Clear();

            }

 

            writer.Write(this.FormatString, list.ToArray());

        }

        else

        {

            base.RenderChildren(writer);

        }

    }

}

 

public class FormattedControlControlBuilder : ControlBuilder

{

    public override Boolean AllowWhitespaceLiterals()

    {

        return false;

    }    

}

Key points are:

  • FormattedControl will not render as default, instead, it will capture the output of all of its child controls into a list and pass it as a parameter to the FormatString string, as {0}, {1}, etc;
  • ParseChildrenAttribute and PersisteChildrenAttribute are used to tell ASP.NET how to treat the controls declared inside FormattedControl;
  • The ControlBuilderAttribute is used to declare a custom control builder; this custom builder just tells ASP.NET to ignore whitespaces inside the control’s tag, otherwise, we would get a LiteralControl for each tab / space / return character present inside the FormattedControl’s tag, together with those controls explicitly declared.

An example:

<my:FormattedControl runat="server" FormatString="{1}, {0}!">

    <asp:Literal runat="server" Text="World"/>

    <asp:Literal runat="server" Text="Hello"/>

</my:FormattedControl>

Of course, you should only use FormattedControl with controls that return string literals, otherwise, you will probably get a lot of garbage in the output.

Porto Tech Hub 2015 – Wrap Up

Porto Tech Hub is over… for now! I was interesting, I got the chance to listen to some good presentations, learn a bit, and see old friends. I really liked the presentations by my former colleagues @ CRITICAL Software, Norberto Leite (@nleite), from MongoDB and Rui Ferreira, from Facebook, and, of course, the one from my friend and colleague João Esteves, which you can grab from the event’s site.

My presentation, Microsoft ♥ Open Source, is now available at my SlideShare. I tried to show a few open source projects and initiatives from Microsoft, so as to demonstrate that it has always supported open source for some time now.

As always, I’d like to hear from you!

Porto Tech Hub 2015

Manifesto

I have the pleasure to announce that I will be speaking next Saturday, May 30th, in the Porto Tech Hub event!

The sad part is, I will be replacing my friend Roberto Cortez (@radcortez), of Java fame, who can’t make it. Anyway, the topic will be Open Source @ Microsoft and I hope to see you all there! Winking smile

Thanks to the organization, namely, to my friend and colleague Luís Ribeiro (@luisafonso) for the invitation, and I hope I am up to it!

Free Training Materials for Development

A random list that will grow, eventually, with time.

Training Kits

Microsoft: https://weblogs.asp.net/ricardoperes/microsoft-developer-training-kits

 

E-books

Syncfusion

http://www.syncfusion.com/resources/techportal/ebooks/

Microsoft

http://blogs.msdn.com/b/mssmallbiz/archive/2014/07/07/largest-collection-of-free-microsoft-ebooks-ever-including-windows-8-1-windows-8-windows-7-office-2013-office-365-office-2010-sharepoint-2013-dynamics-crm-powershell-exchange-server-lync-2013-system-center-azure-cloud-sql.aspx

Others

http://speckyboy.com/2015/01/12/free-web-design-ebooks-2014/

 

Videos

ASP.NET

ASP.NET
MVC: http://www.asp.net/mvc/videos, http://www.asp.net/mvc/videos/mvc-5

ASP.NET Web
Forms: http://www.asp.net/web-forms/videos

ASP.NET
Single Page Application: http://www.asp.net/single-page-application/videos

Web API: http://www.asp.net/web-api/videos

Web Pages: http://www.asp.net/web-pages/videos

ASP.NET MVC
(Pluralsight): http://www.asp.net/mvc/pluralsight

ASP.NET Web
Forms (Pluralsight): http://www.asp.net/web-forms/pluralsight

Microsoft

SharePoint:
https://technet.microsoft.com/en-us/library/cc262880.aspx

How do I?: https://msdn.microsoft.com/en-us/bb629407.aspx

SharePoint
2013 training for IT pros: https://technet.microsoft.com/en-us/office/dn756397.aspx

Azure: http://azure.microsoft.com/en-gb/documentation/videos/index/

Entity Framework: https://msdn.microsoft.com/en-us/data/jj590134.aspx

Data (WCF Data Services, Entity Framework, ADO.NET, LINQ to SQL): https://msdn.microsoft.com/en-us/data/videos.aspx

SharePoint Online: https://support.office.com/en-us/article/SharePoint-Online-training-courses-videos-and-tutorials-2eb5e190-1c90-4987-908d-7c2263f40c5e?CorrelationId=ca3182e4-df93-4d45-8f4c-87e558fd1cbb&ui=en-US&rs=en-US&ad=US&fromAR=1

Office 365 Jump Start: https://technet.microsoft.com/en-us/video/office-365-jump-start-01-microsoft-office-365-overview-for-it-pros.aspx

Channel 9

C#: http://channel9.msdn.com/Search?term=c%23

ASP.NET: http://channel9.msdn.com/Search?term=asp.net

SharePoint:
http://channel9.msdn.com/Search?term=sharepoint

HTML5: http://channel9.msdn.com/Search?term=html5

SQL Server:
http://channel9.msdn.com/Search?term=sql%20server

Azure: http://channel9.msdn.com/Search?term=azure

Office 365: http://channel9.msdn.com/Search?term=office%20365

Entity Framework: http://channel9.msdn.com/Search?term=entity%20framework

Web API: http://channel9.msdn.com/Search?term=web%20api

Microsoft Virtual Academy

C#: http://www.microsoftvirtualacademy.com/training-topics/c-app-development

ASP.NET: http://www.microsoftvirtualacademy.com/training-topics/web-development

SharePoint:
http://www.microsoftvirtualacademy.com/product-training/sharepoint

HTML5: http://www.microsoftvirtualacademy.com/training-topics/html5

SQL Server:
http://www.microsoftvirtualacademy.com/product-training/sql-server

Azure: http://www.microsoftvirtualacademy.com/training-topics/cloud-app-development

Office 365: http://www.microsoftvirtualacademy.com/product-training/office-development

YouTube

C#: https://www.youtube.com/results?search_query=C%23

ASP.NET: https://www.youtube.com/results?search_query=asp.net

SharePoint:
https://www.youtube.com/results?search_query=sharepoint+2013

HTML5: https://www.youtube.com/results?search_query=html5

SQL Server: https://www.youtube.com/results?search_query=sql+server

Azure: https://www.youtube.com/results?search_query=azure

Office 365: https://www.youtube.com/results?search_query=office+365

Entity Framework: https://www.youtube.com/results?search_query=entity+framework

Web API: https://www.youtube.com/results?search_query=web+api

Packt Publishing Free Learning Library

FL - 590x295

From 30th April, 2015 Packt Publishing has thrown open the
virtual doors of its new Free Learning Library and offering its customers a daily chance to grab a fresh free
eBook from its website. The Free Learning Library will be open all year-round but each title will only
be up for 24 hours, so make sure you keep checking back to get your hands on
the latest book! All you’ll have to do is simply click on the day’s free eBook and it will
instantly be added to your account.

See more information here: http://bit.ly/1EXeiCu.

Entity Framework Multitenancy Part 2 – Conventions

In my last post, I talked about different scenarios for achieving multitenancy with Entity Framework contexts. This time, I am going to show how to use conventions, wrapping the code I provided then.

First, a base convention class to serve as our root hierarchy of multitenant conventions:

public abstract class MultitenantConvention : IConvention

{

}

Only worthy of mention is the implementation of IConvention. This is a marker interface for letting Entity Framework know that this is a convention.

Next, a convention for the separate databases approach:

public class SeparateDatabasesConvention : MultitenantConvention

{

    public SeparateDatabasesConvention(DbContext ctx)

    {

        var currentTenantId = TenantConfiguration.GetCurrentTenant();

        ctx.Database.Connection.ConnectionString = ConfigurationManager.ConnectionStrings[currentTenantId].ConnectionString;

    }

}

This convention needs a reference to the DbContext, because it needs to change the connection string dynamically, something that you can’t do through the basic convention interfaces and classes – there’s no way to get to the context.

Now, shared database, different schemas. This time, we need to implement IStoreModelConvention<T>, using EntitySet as the generic parameter, so as to gain access to the Schema property:

public class SharedDatabaseSeparateSchemaConvention : MultitenantConvention, IStoreModelConvention<EntitySet>

{

    public void Apply(EntitySet item, DbModel model)

    {

        var currentTenantId = TenantConfiguration.GetCurrentTenant();

        item.Schema = currentTenantId;

    }

}

Finally, shared database, shared schema:

public class SharedDatabaseSharedSchemaConvention : MultitenantConvention

{

    public String DiscriminatorColumnName { get; private set; }


    private void Map<T>(EntityMappingConfiguration<T> cfg) where T : class

    {

        var currentTenantId = TenantConfiguration.GetCurrentTenant();

        cfg.Requires(this.DiscriminatorColumnName).HasValue(currentTenantId);

    }


    public SharedDatabaseSharedSchemaConvention(DbModelBuilder modelBuilder, String discriminatorColumnName = "Tenant")

    {

        this.DiscriminatorColumnName = discriminatorColumnName;


        var modelConfiguration = modelBuilder.GetType().GetProperty("ModelConfiguration", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(modelBuilder, null);

        var entities = modelConfiguration.GetType().GetProperty("Entities", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(modelConfiguration, null) as IEnumerable<Type>;


        foreach (var entity in entities)

        {

            var entityTypeConfiguration = modelBuilder.GetType().GetMethod("Entity").MakeGenericMethod(entity).Invoke(modelBuilder, null);

            var mapMethod = entityTypeConfiguration.GetType().GetMethods().First(m => m.Name == "Map");


            var localMethod = this.GetType().GetMethod("Map", BindingFlags.Instance | BindingFlags.NonPublic).MakeGenericMethod(entity);

            var delegateType = typeof(Action<>).MakeGenericType(localMethod.GetParameters().First().ParameterType);


            var del = Delegate.CreateDelegate(delegateType, this, localMethod);


            mapMethod.Invoke(entityTypeConfiguration, new Object[] { del });

        }

    }

}

And a nice way to wrap all this using extension methods:

public static class DbModelBuilderExtensions

{

    public static DbModelBuilder UseSeparateDatabases(this DbModelBuilder modelBuilder, DbContext ctx)

    {

        modelBuilder.Conventions.Remove<MultitenantConvention>();

        modelBuilder.Conventions.Add(new SeparateDatabasesConvention(ctx));

        return modelBuilder;

    }

    public static DbModelBuilder UseSharedDatabaseSeparateSchema(this DbModelBuilder modelBuilder)

    {

        modelBuilder.Conventions.Remove<MultitenantConvention>();

        modelBuilder.Conventions.Add(new SharedDatabaseSeparateSchemaConvention());

        return modelBuilder;

    }

    public static DbModelBuilder UseSharedDatabaseSharedSchema(this DbModelBuilder modelBuilder, String discriminatorColumnName = "Tenant")

    {

        modelBuilder.Conventions.Remove<MultitenantConvention>();

        modelBuilder.Conventions.Add(new SharedDatabaseSharedSchemaConvention(modelBuilder, discriminatorColumnName));

        return modelBuilder;

    }

}

Usage: just uncomment one of the Use calls.

protected override void OnModelCreating(DbModelBuilder modelBuilder)

{

    //uncomment one of the following lines

    //modelBuilder.UseSeparateDatabases(this);

    //modelBuilder.UseSharedDatabaseSeparateSchema();

    //modelBuilder.UseSharedDatabaseSharedSchema(discriminatorColumnName: "Tenant");


    base.OnModelCreating(modelBuilder);

}

Entity Framework Multitenancy

Introduction

Multitenancy is currently a hot topic in web development. Azure, SharePoint, and many other frameworks are offering multitenant options, because it totally makes sense to make a better use of a server by hosting many different services.

When it comes to data, there are usually three different strategies:

  • Separate databases: each tenant is stored in its own database; different connection strings must be provided, one for each tenant;
  • Shared database, separate schemas: all tenant’s data live in the same database and even share table names, but in different schemas;
  • Shared database, shared schema tenants share the same physical tables,  but use a discriminator column for distinguishing between them.

Let’s explore how we can use each of these techniques in Entity Framework (Code First, of course – is there another?).

Prerequisites

First, we need to have a way to obtain the current tenant, specifically, a tenant id or code. To simplify, let’s just assume a simple interface:

public static class TenantConfiguration

{

    public static String GetCurrentTenantId()

    {

        //doesn't matter

    }

}

You are free to implement this in any way you want.

We’ll also have a data context:

public class MultitenantContext : DbContext

{

    public DbSet<MultitenantEntity> MultitenantEntities { get; set; }

}

Separate Databases

We need to inject the different connection strings through the constructor:

public MultitenantContext() : base(GetConnectionString())

{

}

 

private static String GetConnectionString()

{

    var currentTenant = TenantConfiguration.GetCurrentTenantId();

    return ConfigurationManager.ConnectionStrings[currentTenant].ConnectionString;

}

This is a simple strategy that relies on having one connection string per tenant id, but others exist, of course.

Shared Database, Separate Schemas

Another option is to have each tenant in its own schema. For that, we need to leverage the OnModelCreating method for configuring the model:

protected override void OnModelCreating(DbModelBuilder modelBuilder)

{

    var currentTenant = TenantConfiguration.GetCurrentTenantId();

 

    modelBuilder.Types().Configure(x =>

    {

        x.ToTable(x.ClrType.Name, currentTenant);

    });

 

    base.OnModelCreating(modelBuilder);

}

Again, a simple example: each type gets mapped to an equally-named table and to a schema that is identical to the tenant id. Pay attention to this: SQL Server and other RDBMSs allows users to have a default schema, so in theory, if you use integrated security, you may leave out the explicit schema. However, Entity Framework will always include the schema with the database objects, so you have to explicitly configure it, as we have.

Shared Database, Shared Schema

The final option depends on a discriminator column that is not mapped, but contains a different value for each tenant. Again, we need to configure the model accordingly (warning: reflection ahead):

private static void Map<T>(EntityMappingConfiguration<T> cfg) where T : class

{

    var currentTenant = TenantConfiguration.GetCurrentTenantId();

 

    cfg.Requires("Tenant").HasValue(currentTenant);

}

 

protected override void OnModelCreating(DbModelBuilder modelBuilder)

{

    var modelConfiguration = modelBuilder.GetType().GetProperty("ModelConfiguration", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(modelBuilder, null);

    var entities = modelConfiguration.GetType().GetProperty("Entities", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(modelConfiguration, null) as IEnumerable<Type>;

 

    foreach (var entity in entities)

    {

        var entityTypeConfiguration = modelBuilder.GetType().GetMethod("Entity").MakeGenericMethod(entity).Invoke(modelBuilder, null);

        var mapMethod = entityTypeConfiguration.GetType().GetMethods().First(m => m.Name == "Map");

 

        var localMethod = this.GetType().GetMethod("Map", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(entity);

        var delegateType = typeof(Action<>).MakeGenericType(localMethod.GetParameters().First().ParameterType);

 

        var del = Delegate.CreateDelegate(delegateType, localMethod);

 

        mapMethod.Invoke(entityTypeConfiguration, new Object[] { del });

    }

 

    base.OnModelCreating(modelBuilder);

}

This code is required because we need to iterate through all mapped entities, and Entity Framework doesn’t expose everything as public APIs. Some parts could be cached for performance or slightly improved, but I leave that as an exercise to you. In the end, all queries to entities will take an additional restriction “WHERE Tenant = @tenant”, where @tenant will take a different value for each tenant and Tenant is a physical column that isn’t mapped, nor does it need to be.

Conclusion

We’ve seen several techniques for mapping multitenant Entity Framework contexts; ultimately, the one we choose will depend on a number of factors. It will mostly be about having more or less isolation of your data.

Lesser-Known NHibernate Features: Calculated Properties

With NHibernate you can have entity properties that are the result of a SQL expression (unlike other O/RMs). It is also possible to add arbitrary SQL restrictions to collections of an entity.

First, here’s how we define a calculated property:

public class MyEntity

{

    //rest goes here

    public Int32 ? MyCalculatedProperty { get; protected set; }

}

 

mapper.Class<MyEntity>(c =>

{

    //rest goes here

    c.Property(x => x.MyCalculatedProperty, x =>

    {

        x.Formula("(SELECT MAX(SomeTable.Something) FROM SomeTable WHERE SomeTable.Id = Id)");

        x.Insert(false);

        x.Update(false);

    });

});

NHibernate is clever enough to find out that the un-prefixed Id refers to the entity’s table. This is a silly example, but I think you get the picture. Remember that this is plain SQL, not HQL, and will not be translated in any way.

As for collection restrictions, a simple example:

public class MyEntity

{

    //rest goes here

    public virtual IEnumerable<MyIssue> RecentIssues { get; protected set; }

}

 

mapper.Class<MyEntity>(c =>

{

    //rest goes here

    c.Set(x => x.RecentIssues, x =>

    {

        c.Where("(date >= (GETDATE() - 7))");

        //rest goes here

    }, c => c.OneToMany());

});

Notice that I am mapping the RecentIssues collection as IEnumerable<T>, this is because otherwise I would have to check if the values being added matched the desired constraint (“>= GETDATE() – 7”, the last 7 days). Certainly possible, but I leave it as an exercise to you, dear reader! Of course, GETDATE() is a SQL Server function, the restrictions can only be specified in native SQL.

Stay tuned for more!

Lesser-known NHibernate Features: Filters

Unlike other OR/Ms – which, as always, shall remain unnamed – NHibernate offers a couple of ways to automatic filter results. Basically, we have two options:

  • Static restrictions;
  • Dynamic restrictions, or filters.

Because filters offer everything that static restrictions do and more, we’ll focus on filters.

A filter can specify a restriction, in terms of a SQL clause, to either an entity as a whole (the class, not a specific query) or to a collection (bag, set, list, map, array, etc).

For example, imagine you have a table that holds values that can be translated and a translation table for that purpose:

image

You will want to retrieve only the translation for the current culture. A domain model could look like:

image

We would like to apply a restriction to the Translations property of Translatable, so as to filter the translations by the current culture.

First, we need to create a filter, this is done at Configuration level:

cfg.AddFilterDefinition(new FilterDefinition("CurrentCulture", string.Empty, new Dictionary<string, IType> { { "Culture", NHibernateUtil.String } }, false));

The restriction can be defined on the filter itself, or per entity or collection. In this case, I didn’t specify it on the filter (string.Empty), so I will need to do it at the collection level:

mapper.Class<Translatable>(x =>

{

        //rest goes here

        x.Set(y => y.Translations, y =>

        {

            //rest goes here

            y.Filter("CurrentCulture", z =>

            {

                z.Condition("Culture = :Culture");

            });

        });

    }

);

A filter needs to be explicitly made active, and, if it contains parameters, all of its parameters must be set:

session.EnableFilter("CurrentCulture").SetParameter("Culture", CultureInfo.CurrentCulture.Name);

Now, whenever the Translations collection is retrieved, either through a SELECT or an INNER JOIN, the “Culture = :Culture” restriction – where, of course, :Culture is replaced by the current parameter value – will be applied automatically, together with the foreign key restriction.

The other option is to filter entities as a whole. Remember soft deletes? I wrote two posts on them (here and here). Instead of using static restrictions, we can instead use filters:

cfg.AddFilterDefinition(new FilterDefinition("SoftDeletes", "deleted = 0, new Dictionary<string, IType>(), true));

 

mapper.Class<Record>(x =>

{

    x.Filter("SoftDeletes", y => {});

    x.Set(y => y.Children, y =>

        {

            y.Filter("SoftDeletes", z => {});

        });

});

In this example, I define the restriction string on the filter itself, and I apply it to both the Record entity and its Children collection. This time, no parameters, but we still need to enable the filter before we issue a query:

session.EnableFilter("SoftDeletes");

If for any reason you want to disable a filter, it’s easy:

session.DisableFilter("SoftDeletes");

Or even get its definition:

var filter = sessionFactory.GetFilterDefinition("SoftDeletes");

Enjoy your filters!

Defining ASP.NET Update Panel Template Contents Dynamically

The ASP.NET UpdatePanel was introduced with the ASP.NET 2.0 AJAX Extensions almost a century ago (kidding, but almost feels like it!Winking smile). It allows us to have AJAX-style effects (partial page loads) with very little effort.

The UpdatePanel has a property, ContentTemplate, which is, well, a template, which means it can only be set through markup – not so good for dynamic contents -, or by implementing our own ITemplate class – slightly more work, but this can be reused in other scenarios where an ITemplate is required:

public class Template : Control, ITemplate

{

    public void InstantiateIn(Control container)

    {

        var controls = this.Controls.OfType<Control>().ToList();

 

        for (var i = 0; i < controls.Count; i++)

        {

            var control = controls[i];

            container.Controls.Add(control);

        }

    }

}

 

protected override void OnLoad(EventArgs e)

{

    var tb = new TextBox { ID = "time" };

    var timer = new Timer { Interval = 1000, Enabled = true };

    timer.Tick += timer_Tick;

 

    var tmp = new Template();

    tmp.Controls.Add(tb);

    tmp.Controls.Add(timer);

 

    var up = new UpdatePanel();

    up.ContentTemplate = tmp;

 

    this.Form.Controls.Add(up);

 

    base.OnLoad(e);

}

 

void timer_Tick(object sender, EventArgs e)

{

    var time = (sender as Control).NamingContainer.FindControl("time") as TextBox;

    time.Text = DateTime.Now.ToString();

}

There is also a property, ContentTemplateContainer, which is a control to which you can add your controls, so that they are added to the UpdatePanel. This is exactly what we need:

protected override OnLoad(EventArgs e)

{

    var tb = new TextBox { ID = "time" };

    var timer = new Timer { Interval = 1000, Enabled = true };

    timer.Tick += this.timer_Tick;

    

    var up = new UpdatePanel();

    up.ContentTemplateContainer.Controls.Add(tb);

    up.ContentTemplateContainer.Controls.Add(timer);

    

    this.Form.Controls.Add(up);

    

    base.OnLoad(e);

}