Hibernating Rhinos

Zero friction databases

RavenDB in Practice, Part 2 - Using the Client API

This series is about how to start using RavenDB without any prior knowledge about Document Databases. There is assumption that you’re familiar with Relational Databases like Sql Server, though.

In the introduction post of this series I briefly examined what is a Document Database, what is a document and how RavenDB Management Studio looks like. Now it’s time to show you how to actually use RavenDB in your applications. So let’s talk about the client API.

The target of this post is to show you how to use the client API: we’ll start by creating a basic model entity, than we’re going to store an instance of this entity in RavenDB database, then load a entity and update it. Finally, I’m going to show you how to do some more complex queries.

You can use RavenDB from any language or platform (more on that in a future blog post). In this blog post we’re going to use the .NET client API which let you consume RavenDB from any .NET application very easily.

Let’s create an empty ASP.NET MVC application and add the following the RavenDB NuGet package to it:

InstallNuGetPakcage_thumb[1]

After the RavenDB NuGet package has been installed you’re ready to start use RavenDB from the MVC application that we created. But we need first need to make sure that we have a RavenDB database to connect to. In order to that, run the RavenDB console application as we did in the previous post, and edit the connection string in the web.config to point to the correct server URL:

  <connectionStrings>
    <add name="RavenDB" connectionString="Url=http://localhost:8080" />
  </connectionStrings>

Now that we have RavenDB and the Client API set up, it’s time to do something interesting with it. In order to start interacting with RavenDB with the Client API you’ll need to create an instance of the IDocumentSession and the IDocumentStore. The session is define the boundaries of your Unit Of Work, which will be in a typical web application the entire web request, and the IDocumentStore lets you create sessions. It’s a good practice to create one instance of IDocumentStore per application and one session per unit of work.

Showing how to effectively managing the session in a web application is out of the scope of this blog post (which require us talking us to talk about IoC patterns), but you can take a look on the RaccoonBlog project in order to see one simple approach to achieve this. The RaccoonBlog project was created specifically for demonstration purposes in this blog posts series and it’s now the blog engine that we use to run this blog).

For this demonstration we’re going to create the session and the document store directly in the controller. As specified in the above paragraph, in real applications you’ll typically want to manage the session out of the controller.

public class ProductController : Controller
{
    private static readonly IDocumentStore documentStore;
    private IDocumentSession _session;

    static ProductController()
    {
        documentStore = new DocumentStore
                            {
                                ConnectionStringName = "RavenDB"
                            }.Initialize();

    }

    protected override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        _session = documentStore.OpenSession();
    }

    protected override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        _session.SaveChanges();
        _session.Dispose();
    }
}

Now that we have the session in place, we can start using it to do CRUD operations on the database. Consider the following Product entity which we’re going to use in this blog post:

public class Product
{
    public string Id { get; set; }
    public string CategoryId { get; set; }
    public string SupplierId { get; set; }

    public string Name { get; set; }
    public string Code { get; set; }
    public decimal StandardCost { get; set; }
    public decimal ListPrice { get; set; }
    public int UnitsOnStock { get; set; }
    public int UnitsOnOrder { get; set; }
    public bool Discontinued { get; set; }

    public string PhotoFile { get; set; }
    public DateTime CreatedAt { get; set; }
}

Let’s create an instance of this entity and store it into the database:

public ActionResult StoreSomeProductInDatabase()
{
    var product = new Product
                    {
                        Name = "Product Name",
                        CategoryId = "category/1024",
                        SupplierId = "supplier/16",
                        Code = "H11050",
                        CreatedAt = DateTime.Now,
                        StandardCost = 250,
                        ListPrice = 189,
                        FilePhoto = "path to picture.jpg",
                    };
    _session.Store(product);
    _session.SaveChanges();

    return Content(product.Id);
}

First we’re creating a product and storing it in the session. I said we’re storing it in the session because when you call the _session.Store(product) method it wouldn’t send the product to the database yet. In order to actually save the product in the database you’ll need to call the _session.SaveChanges(). Note that after you call the _session.SaveChanges() method we can use the product.Id, which will be “products/1”. We didn’t assign that Id manually but let the client API assigns it to us automatically. If you’ll look on the RavenDB console application you’ll see the following log output:

Raven is ready to process requests. Build 385, Version 1.0.0.0 / 64f1188
Server started in 2,519 ms
Data directory: C:\Users\Fitzchak\Downloads\RavenDB-Unstable-Build-385\Server\Data
HostName: <any> Port: 8080, Storage: Esent
Server Url: http://fitzchak-pc:8080/
Press <enter> to stop or 'cls' and <enter> to clear the log
Request #   1: GET     -    26 ms - <default>  - 404 - /docs/Raven/Replication/Destinations
Request #   2: GET     -     0 ms - <default>  - 404 - /docs/Raven/Hilo/products
Request #   3: PUT     -   241 ms - <default>  - 201 - /docs/Raven/Hilo/products
Request #   4: POST    -    44 ms - <default>  - 200 - /bulk_docs
        PUT products/1

At request #4 we can see the actual post request which will store the products/1 document. We got response status #200 which means that the document is successfully stored in the database. In addition you can see that we do not need to query the database and ask what was the generated id for our product (which is a typically case with auto increase field implementation in relational databases), because the Id is generated by the client API (which use the HiLo algorithm to generate it). This “magic” happens in request #2 and #3 which we’ll be covered in the upcoming posts.

You can look on the RavenDB Management Studio in order to see the documents that we just stored:

ProductDocument

We could have also store a lot of products in the session and than call the SaveChanges which will insert all of them to the database with just one call:

public ActionResult InsertSomeMoreProducts()
{
    for (int i = 0; i < 50; i++)
    {
        var product = new Product
        {
            Name = "Product Name " + i,
            CategoryId = "category/1024",
            SupplierId = "supplier/16",
            Code = "H11050" + i,
            CreatedAt = DateTime.Now,
            StandardCost = 250 + (i * 10),
            ListPrice = 189 + (i * 10),
        };
        _session.Store(product);
    }
            
    _session.SaveChanges();

    return Content("Products successfully created");
}

This will create 50 documents in the database with just one call to the database, as you can see in the RavenDB console output (I substitute some of the log output with … for space reasons):

Request #   5: POST    -    17 ms - <default>  - 200 - /bulk_docs
        PUT products/2
        PUT products/3
        PUT products/4
        PUT products/5
        ...
        PUT products/49
        PUT products/50
        PUT products/51

This how it’s looks like on the Management Studio:

SomeMoreStoredDocumets

Now that we have a few documents in the database it’s time to see how we can load one of them. In order to load the entire document you can use the Load method:

Product product = _session.Load<Product>("products/5");
Product product2 = _session.Load<Product>("5");

The above lines are equivalent, they return the same product. The second line is a handy shortcut that RavenDB Client API provides us, which can come in handy when you expose the int part of the ID as a web parameter like in the following code:

public ActionResult GetProduct(int id)
{
    Product product = _session.Load<Product>(id);
    return Content(product.Name);
}

In the above code we used a number in order to get the document instead of having you use the actual full ID (“products/” + id). Now it’s time to modify the product and save it to the database:

public ActionResult LoadAndUpdateProduct()
{
    Product product = _session.Load<Product>("products/5");
    product.ListPrice -= 10;
    _session.SaveChanges();
    return Content("Product 5 successfully updated");
}

As you can see, updating a document is as simple as loading the document, changing its properties and calling the _session.SaveChanges method. This can be happening because that the session keeps track of each of the loaded entities. This lets the session determine which of the documents has been updated and send just them to the database.

What is left is to delete a document. Consider the following code which will do exactly that:

public ActionResult DeleteProduct(int id)
{
    Product product = _session.Load<Product>(id);
    if (product == null)
        return HttpNotFound("Product {0} does not exist");
    _session.Delete(product);
    _session.SaveChanges();
    return Content(string.Format("Product {0} successfully deleted", id));
}

One interesting thing that we do here is to check if product is null. This is very important step after loading a document because that _session.Load method will return null if there is no document with the corresponding specified id in the database.

Until now we did plain CRUD operations. Now it’s time to create some more complex queries... Raven Client API comes with a great support for Linq so we can use this in order to create more complex queries. Here in an example of how we can get all the products that are available for sale (discontinued equal to false) ordered by the product’s list price:

public ActionResult GetDiscontinuedProducts()
{
    var products = from product in _session.Query<Product>()
                   where product.Discontinued == false
                   orderby product.ListPrice
                   select product;

    return View(products.ToList());
}

As you can see, RavenDB Client API gives you what you’ll expect from a modern database client API to support. It has great LINQ provider that you’ll let you do almost anything you can do with a regular LINQ queries.

In this post I demonstrated how easy is to use the RavenDB .NET Client API in order to store some documents in the database, load them, update and remove them. Than we saw that we can use LINQ powered queries in order to actually query the database with some criteria and order by statement.

In the future blog posts I’m going to talk about the following topics:

  • Introduce the RaccoonBlog project and blog about the interesting parts of it.
  • Talk about modeling entities in a way that suits documents database.

If you have any feedback / questions / suggestions regards this blog posts I’ll be more than happy to hear it.

Tags:

Published at

Originally posted at

Comments (8)

RavenDB in Practice, Part 2 - Using the Client API

This series is about how to start using RavenDB without any prior knowledge about Document Databases. There is assumption that you’re familiar with Relational Databases like Sql Server, though.

In the introduction post of this series I briefly examined what is a Document Database, what is a document and how RavenDB Management Studio looks like. Now it’s time to show you how to actually use RavenDB in your applications. So let’s talk about the client API.

The target of this post is to show you how to use the client API: we’ll start by creating a basic model entity, than we’re going to store an instance of this entity in RavenDB database, then load a entity and update it. Finally, I’m going to show you how to do some more complex queries.

You can use RavenDB from any language or platform (more on that in a future blog post). In this blog post we’re going to use the .NET client API which let you consume RavenDB from any .NET application very easily.

Let’s create an empty ASP.NET MVC application and add the following the RavenDB NuGet package to it:

InstallNuGetPakcage_thumb[1]

After the RavenDB NuGet package has been installed you’re ready to start use RavenDB from the MVC application that we created. But we need first need to make sure that we have a RavenDB database to connect to. In order to that, run the RavenDB console application as we did in the previous post, and edit the connection string in the web.config to point to the correct server URL:

  <connectionStrings>
    <add name="RavenDB" connectionString="Url=http://localhost:8080" />
  </connectionStrings>

Now that we have RavenDB and the Client API set up, it’s time to do something interesting with it. In order to start interacting with RavenDB with the Client API you’ll need to create an instance of the IDocumentSession and the IDocumentStore. The session is define the boundaries of your Unit Of Work, which will be in a typical web application the entire web request, and the IDocumentStore lets you create sessions. It’s a good practice to create one instance of IDocumentStore per application and one session per unit of work.

Showing how to effectively managing the session in a web application is out of the scope of this blog post (which require us talking us to talk about IoC patterns), but you can take a look on the RaccoonBlog project in order to see one simple approach to achieve this. The RaccoonBlog project was created specifically for demonstration purposes in this blog posts series and it’s now the blog engine that we use to run this blog).

For this demonstration we’re going to create the session and the document store directly in the controller. As specified in the above paragraph, in real applications you’ll typically want to manage the session out of the controller.

public class ProductController : Controller
{
    private static readonly IDocumentStore documentStore;
    private IDocumentSession _session;

    static ProductController()
    {
        documentStore = new DocumentStore
                            {
                                ConnectionStringName = "RavenDB"
                            }.Initialize();

    }

    protected override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        _session = documentStore.OpenSession();
    }

    protected override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        _session.SaveChanges();
        _session.Dispose();
    }
}

Now that we have the session in place, we can start using it to do CRUD operations on the database. Consider the following Product entity which we’re going to use in this blog post:

public class Product
{
    public string Id { get; set; }
    public string CategoryId { get; set; }
    public string SupplierId { get; set; }

    public string Name { get; set; }
    public string Code { get; set; }
    public decimal StandardCost { get; set; }
    public decimal ListPrice { get; set; }
    public int UnitsOnStock { get; set; }
    public int UnitsOnOrder { get; set; }
    public bool Discontinued { get; set; }

    public string PhotoFile { get; set; }
    public DateTime CreatedAt { get; set; }
}

Let’s create an instance of this entity and store it into the database:

public ActionResult StoreSomeProductInDatabase()
{
    var product = new Product
                    {
                        Name = "Product Name",
                        CategoryId = "category/1024",
                        SupplierId = "supplier/16",
                        Code = "H11050",
                        CreatedAt = DateTime.Now,
                        StandardCost = 250,
                        ListPrice = 189,
                        FilePhoto = "path to picture.jpg",
                    };
    _session.Store(product);
    _session.SaveChanges();

    return Content(product.Id);
}

First we’re creating a product and storing it in the session. I said we’re storing it in the session because when you call the _session.Store(product) method it wouldn’t send the product to the database yet. In order to actually save the product in the database you’ll need to call the _session.SaveChanges(). Note that after you call the _session.SaveChanges() method we can use the product.Id, which will be “products/1”. We didn’t assign that Id manually but let the client API assigns it to us automatically. If you’ll look on the RavenDB console application you’ll see the following log output:

Raven is ready to process requests. Build 385, Version 1.0.0.0 / 64f1188
Server started in 2,519 ms
Data directory: C:\Users\Fitzchak\Downloads\RavenDB-Unstable-Build-385\Server\Data
HostName: <any> Port: 8080, Storage: Esent
Server Url: http://fitzchak-pc:8080/
Press <enter> to stop or 'cls' and <enter> to clear the log
Request #   1: GET     -    26 ms - <default>  - 404 - /docs/Raven/Replication/Destinations
Request #   2: GET     -     0 ms - <default>  - 404 - /docs/Raven/Hilo/products
Request #   3: PUT     -   241 ms - <default>  - 201 - /docs/Raven/Hilo/products
Request #   4: POST    -    44 ms - <default>  - 200 - /bulk_docs
        PUT products/1

At request #4 we can see the actual post request which will store the products/1 document. We got response status #200 which means that the document is successfully stored in the database. In addition you can see that we do not need to query the database and ask what was the generated id for our product (which is a typically case with auto increase field implementation in relational databases), because the Id is generated by the client API (which use the HiLo algorithm to generate it). This “magic” happens in request #2 and #3 which we’ll be covered in the upcoming posts.

You can look on the RavenDB Management Studio in order to see the documents that we just stored:

ProductDocument

We could have also store a lot of products in the session and than call the SaveChanges which will insert all of them to the database with just one call:

public ActionResult InsertSomeMoreProducts()
{
    for (int i = 0; i < 50; i++)
    {
        var product = new Product
        {
            Name = "Product Name " + i,
            CategoryId = "category/1024",
            SupplierId = "supplier/16",
            Code = "H11050" + i,
            CreatedAt = DateTime.Now,
            StandardCost = 250 + (i * 10),
            ListPrice = 189 + (i * 10),
        };
        _session.Store(product);
    }
            
    _session.SaveChanges();

    return Content("Products successfully created");
}

This will create 50 documents in the database with just one call to the database, as you can see in the RavenDB console output (I substitute some of the log output with … for space reasons):

Request #   5: POST    -    17 ms - <default>  - 200 - /bulk_docs
        PUT products/2
        PUT products/3
        PUT products/4
        PUT products/5
        ...
        PUT products/49
        PUT products/50
        PUT products/51

This how it’s looks like on the Management Studio:

SomeMoreStoredDocumets

Now that we have a few documents in the database it’s time to see how we can load one of them. In order to load the entire document you can use the Load method:

Product product = _session.Load<Product>("products/5");
Product product2 = _session.Load<Product>("5");

The above lines are equivalent, they return the same product. The second line is a handy shortcut that RavenDB Client API provides us, which can come in handy when you expose the int part of the ID as a web parameter like in the following code:

public ActionResult GetProduct(int id)
{
    Product product = _session.Load<Product>(id);
    return Content(product.Name);
}

In the above code we used a number in order to get the document instead of having you use the actual full ID (“products/” + id). Now it’s time to modify the product and save it to the database:

public ActionResult LoadAndUpdateProduct()
{
    Product product = _session.Load<Product>("products/5");
    product.ListPrice -= 10;
    _session.SaveChanges();
    return Content("Product 5 successfully updated");
}

As you can see, updating a document is as simple as loading the document, changing its properties and calling the _session.SaveChanges method. This can be happening because that the session keeps track of each of the loaded entities. This lets the session determine which of the documents has been updated and send just them to the database.

What is left is to delete a document. Consider the following code which will do exactly that:

public ActionResult DeleteProduct(int id)
{
    Product product = _session.Load<Product>(id);
    if (product == null)
        return HttpNotFound("Product {0} does not exist");
    _session.Delete(product);
    _session.SaveChanges();
    return Content(string.Format("Product {0} successfully deleted", id));
}

One interesting thing that we do here is to check if product is null. This is very important step after loading a document because that _session.Load method will return null if there is no document with the corresponding specified id in the database.

Until now we did plain CRUD operations. Now it’s time to create some more complex queries... Raven Client API comes with a great support for Linq so we can use this in order to create more complex queries. Here in an example of how we can get all the products that are available for sale (discontinued equal to false) ordered by the product’s list price:

public ActionResult GetDiscontinuedProducts()
{
    var products = from product in _session.Query<Product>()
                   where product.Discontinued == false
                   orderby product.ListPrice
                   select product;

    return View(products.ToList());
}

As you can see, RavenDB Client API gives you what you’ll expect from a modern database client API to support. It has great LINQ provider that you’ll let you do almost anything you can do with a regular LINQ queries.

In this post I demonstrated how easy is to use the RavenDB .NET Client API in order to store some documents in the database, load them, update and remove them. Than we saw that we can use LINQ powered queries in order to actually query the database with some criteria and order by statement.

In the future blog posts I’m going to talk about the following topics:

  • Introduce the RaccoonBlog project and blog about the interesting parts of it.
  • Talk about modeling entities in a way that suits documents database.

If you have any feedback / questions / suggestions regards this blog posts I’ll be more than happy to hear it.

Tags:

Published at

Originally posted at

Comments (10)

NuGet packages and VB.net application start code

In order to support one click install experience for our profilers we’re providing our users with NuGet packages for some of our profilers. The profilers that currently have NuGet package are:

When you install one of the profiler packages, your project is updated with some application start code that wiring your application with the profiler. This is great but it’s even greater since we are fully support VB projects alongside the C# once.

One thing that it’s interesting through is that no matter what project type you’re using, C# or VB, both files types are added to your project, one for each project type:

NuGetAppStartFiles

You may think that you’re going to get compilation error here, as C# projects can’t read the .vb file and vice versa, VB projects can’t read the .cs file, but it’s actually do compile. This is because that only one of the files are going to compile with your code while the other one added as content – which means that even that the file is there you actually do not do anything interesting with it - it’s not going to compile with your code.

In a C# project the files properties will like the following:

NuGetAppStartCSFile

The .cs file build action value is compile.

NuGetAppStartVBFile

The .vs file build action set value is content.

It’s may not be very intuitive to see a .vb file in C# project, but this behavior is inherited by NuGet, which is responsible to add those files. Looking on the NuGet documentation I wasn’t able to see any reference regards how is it behaves on different project types.

We can remove one of the files manually from the Install.ps1 files, depends on what project type you use, but since we think that this responsibility is of NuGet, we let NuGet to take care of that.

Tags:

Published at

Originally posted at

NuGet packages and VB.net application start code

In order to support one click install experience for our profilers we’re providing our users with NuGet packages for some of our profilers. The profilers that currently have NuGet package are:

When you install one of the profiler packages, your project is updated with some application start code that wiring your application with the profiler. This is great but it’s even greater since we are fully support VB projects alongside the C# once.

One thing that it’s interesting through is that no matter what project type you’re using, C# or VB, both files types are added to your project, one for each project type:

NuGetAppStartFiles

You may think that you’re going to get compilation error here, as C# projects can’t read the .vb file and vice versa, VB projects can’t read the .cs file, but it’s actually do compile. This is because that only one of the files are going to compile with your code while the other one added as content – which means that even that the file is there you actually do not do anything interesting with it - it’s not going to compile with your code.

In a C# project the files properties will like the following:

NuGetAppStartCSFile

The .cs file build action value is compile.

NuGetAppStartVBFile

The .vs file build action set value is content.

It’s may not be very intuitive to see a .vb file in C# project, but this behavior is inherited by NuGet, which is responsible to add those files. Looking on the NuGet documentation I wasn’t able to see any reference regards how is it behaves on different project types.

We can remove one of the files manually from the Install.ps1 files, depends on what project type you use, but since we think that this responsibility is of NuGet, we let NuGet to take care of that.

Tags:

Published at

Originally posted at

One click install NuGet Package and Console Applications

We started to provide NuGet packages for some of our profilers about 2 months ago. It’s provided you with a great one click install experience: all you have to do in order to start profiling your application is to install the NuGet package of the appropriate profiler and that’s it. The profiler application is opened up for you and your application is set up, all you left to do is to actually go to business and start profiling your application. This is a great on install experience, that we are proud to provide you with.

This is our currently provided NuGet packages:

But we did have one problem with it. It didn’t work well with non-web based applications. That’s because that the way to provide one click install experience with NuGet packages is by using a package named WebActivator. You can read more about the way that WebActivator works here, but in nutshell, it provided the package with a way to specify the method that should be run as part of the web application start process, by specifying an assembly attribute like the following one:

[assembly: WebActivator.PreApplicationStartMethod(typeof($rootnamespace$.App_Start.EntityFrameworkProfilerBootstrapper), "PreStart")]

But as I mentioned, this is works only with web-based projects. When you use with non-web based application, like a Console Application for example, things are starting to get weird. This is because that the WebActivor has a dependency on System.Web.dll which non-web projects not include a reference to it be default. Even more, the default template for Console Application in Visual Studio 2010 uses the .NET Framework Client Profile which does not include the System.Web.dll in it. So when you create a console application and install one of the profiler’s NuGet packages you get a mysterious compilation error:

The type or namespace name 'WebActivator' could not be found (are you missing a using directive or an assembly reference?)

This error is confusing because if you’ll take a look on the references of your project you will see a reference to the WebActivator. The missing assembly reference is actually the System.Web.dll. But in order to add a reference to System.Web you have to target the Full .Net Framework in your project. But those are not recommended steps to do, since that if you’ll do them your project will compile fine but you gained nothing from the use of WebActivator since the code that takes care to initialize the profiler (the PreStart method) does not execute. This break down the one click installs experience of the NuGet package for non-web projects.

So how are we going to solve this? Let’s take one problem by one. The first problem was that non-web projects (specifically those that even can’t use it, like projects that use the .Net Framework Client Profile) cannot compile because of the call to WebActivator. The solution for this is simple. We drop the call to WebActivator assembly attribute in non-web projects.

This is done by the Install.ps1 script included in the NuGet package. In order to determine whether the current project is a web project or not we determine if the web.config file exists on the target project. If it’s not, that’s a good indicator that this is not web project and the call to the WebActivator.PreApplicationStartMethod is removed by the scripts' code.

This is important step to eliminate the confusing compilation error, but we have a second problem: the initialization code is never run and this break the one click install experience that we so want to provide. What will be the solution for this? Well, a simple solution may be is to drop a call to the PreStart method of the profiler bootstrapper in the start code or your application.

But where is exactly the application’s start code? This can be vary depends on the current project type (WinForms or WPF for example) and the user’s actual code structure. Covering all the option of where is code can be can be very hard task to do. So what we decided to do is to cover just the simple case of a default console application. If you have a console application with a Program.cs file, we’ll drop a call to the App_Start.PreStart method in your Main method (assuming it exists).

This will cover the very basic case of when you just want to see how to work with the profiler by creating a console application and installing the profiler’s NuGet package. In this case it can be acceptable to edit the user’s code by the NuGet script. But this won’t interrupt you in more advances cases when you use other types of non-web projects like WinFroms or WPF application.

This is how the NuGet packages behaving right now. We think that this fairly elegant solution for what we can do with the NuGet package and we want to hear from you how this is working out for you!

Tags:

Published at

Originally posted at

Comments (1)

One click install NuGet Package and Console Applications

We started to provide NuGet packages for some of our profilers about 2 months ago. It’s provided you with a great one click install experience: all you have to do in order to start profiling your application is to install the NuGet package of the appropriate profiler and that’s it. The profiler application is opened up for you and your application is set up, all you left to do is to actually go to business and start profiling your application. This is a great on install experience, that we are proud to provide you with.

This is our currently provided NuGet packages:

But we did have one problem with it. It didn’t work well with non-web based applications. That’s because that the way to provide one click install experience with NuGet packages is by using a package named WebActivator. You can read more about the way that WebActivator works here, but in nutshell, it provided the package with a way to specify the method that should be run as part of the web application start process, by specifying an assembly attribute like the following one:

[assembly: WebActivator.PreApplicationStartMethod(typeof($rootnamespace$.App_Start.EntityFrameworkProfilerBootstrapper), "PreStart")]

But as I mentioned, this is works only with web-based projects. When you use with non-web based application, like a Console Application for example, things are starting to get weird. This is because that the WebActivor has a dependency on System.Web.dll which non-web projects not include a reference to it be default. Even more, the default template for Console Application in Visual Studio 2010 uses the .NET Framework Client Profile which does not include the System.Web.dll in it. So when you create a console application and install one of the profiler’s NuGet packages you get a mysterious compilation error:

The type or namespace name 'WebActivator' could not be found (are you missing a using directive or an assembly reference?)

This error is confusing because if you’ll take a look on the references of your project you will see a reference to the WebActivator. The missing assembly reference is actually the System.Web.dll. But in order to add a reference to System.Web you have to target the Full .Net Framework in your project. But those are not recommended steps to do, since that if you’ll do them your project will compile fine but you gained nothing from the use of WebActivator since the code that takes care to initialize the profiler (the PreStart method) does not execute. This break down the one click installs experience of the NuGet package for non-web projects.

So how are we going to solve this? Let’s take one problem by one. The first problem was that non-web projects (specifically those that even can’t use it, like projects that use the .Net Framework Client Profile) cannot compile because of the call to WebActivator. The solution for this is simple. We drop the call to WebActivator assembly attribute in non-web projects.

This is done by the Install.ps1 script included in the NuGet package. In order to determine whether the current project is a web project or not we determine if the web.config file exists on the target project. If it’s not, that’s a good indicator that this is not web project and the call to the WebActivator.PreApplicationStartMethod is removed by the scripts' code.

This is important step to eliminate the confusing compilation error, but we have a second problem: the initialization code is never run and this break the one click install experience that we so want to provide. What will be the solution for this? Well, a simple solution may be is to drop a call to the PreStart method of the profiler bootstrapper in the start code or your application.

But where is exactly the application’s start code? This can be vary depends on the current project type (WinForms or WPF for example) and the user’s actual code structure. Covering all the option of where is code can be can be very hard task to do. So what we decided to do is to cover just the simple case of a default console application. If you have a console application with a Program.cs file, we’ll drop a call to the App_Start.PreStart method in your Main method (assuming it exists).

This will cover the very basic case of when you just want to see how to work with the profiler by creating a console application and installing the profiler’s NuGet package. In this case it can be acceptable to edit the user’s code by the NuGet script. But this won’t interrupt you in more advances cases when you use other types of non-web projects like WinFroms or WPF application.

This is how the NuGet packages behaving right now. We think that this fairly elegant solution for what we can do with the NuGet package and we want to hear from you how this is working out for you!

Tags:

Published at

Originally posted at

Comments (1)