PAGING DATA, NOT CLIENT SIDE (2) (Blazor client)


In the previous post,Paging data, NOT client side. | Universidad Net, I described how to use OFFSET-FETCH pair to paginate data in the server side. In this one, I will describe an example of how to use it in a Web Assembly App.

Note: To test this code, you must create a new Blazor app in Visual Studio.

The Model.

The data model will be a class with two properties, one for the list of items and other with the page’s information.

This classes will be added in the Shared project of the Application.

The first class vary depending on the data you want to display, but the second one will be always the same, enhanced by some code.

Since the pager class will be a standardized and enhanced version of the data retrieved by using the stored procedure, and could be used in different projects, let’s define an interface for it.

IPagesInfo interface.

This is the code for the Interface.

public interface IPagesInfo
{
   #region Properties
 
   /// <summary>
   /// Gets or sets the current page.....
   /// </summary>
   System.Int32 CurrentPage { getset; }
 
   /// <summary>
   /// Gets the current first page number..
   /// </summary>
   Int32 FirstPageNumber { get; }
 
   /// <summary>
   /// Gets the HasNextGroup
   /// Gets a value indicating whether this instance has next page..
   /// </summary>
   Boolean HasNextGroup { get; }
 
   /// <summary>
   /// Gets the HasPreviousGroup
   /// Gets a value indicating whether this instance has previous page..
   /// </summary>
   Boolean HasPreviousGroup { get; }
 
   /// <summary>
   /// Gets the last page number..
   /// </summary>
   Int32 LastPageNumber { get; }
 
   /// <summary>
   /// Gets or sets the number of links to show..
   /// </summary>
   Int32 NumberOfLinks { getset; }
 
   /// <summary>
   /// Gets or sets the size of the page.....
   /// </summary>
   System.Int32 PageSize { getset; }
 
   /// <summary>
   /// Gets or sets the Qty of rows skipped from the top of the select.....
   /// </summary>
   System.Int32 Skip { getset; }
 
   /// <summary>
   /// Gets or sets the Qty of rows taken.....
   /// </summary>
   System.Int32 Take { getset; }
 
   /// <summary>
   /// Gets or sets the amount of items to display.
   /// </summary>
   System.Int32 TotalItems { getset; }
 
   /// <summary>
   /// Gets the total pages available to display..
   /// </summary>
   Int32 TotalPages { get; }
   /// <summary>
   /// Gets the page number for the previous group start.
   /// </summary>
   /// <value>
   /// The page number.
   /// </value>
   Int32 PreviousGroupStart { get; }
   /// <summary>
   /// Gets the  page number for the next group start.
   /// </summary>
   /// <value>
   /// The page number.
   /// </value>
   Int32 NextGroupStart { get; }
   #endregion
}

PagerInfo class.

Here, you have the code for the class implementing the IPagesInfo interface.

Notice the class is responsible of the calculations about page numbers displayed, if there are next or previous groups of pages, etc.

public class PagerInfo : IPagesInfo
{
   #region Fields
 
   /// <summary>
   /// Defines the lastPageNumber.
   /// </summary>
   internal Int32 lastPageNumber = 0;
 
   #endregion
 
   #region Properties
   public Int32 TotalPages
   {
      get => (Int32)Math.Ceiling(TotalItems / (Double)PageSize);
   }
   /// <summary>
   /// Gets the page number for the previous group start.
   /// </summary>
   /// <value>
   /// The page number.
   /// </value>
   public Int32 PreviousGroupStart => LastPageNumber - NumberOfLinks;
   /// <summary>
   /// Gets the  page number for the next group start.
   /// </summary>
   /// <value>
   /// The page number.
   /// </value>
   public Int32 NextGroupStart => LastPageNumber + 1;
 
   /// <summary>
   /// Gets or sets the current page.
   /// </summary>
   public System.Int32 CurrentPage { getset; }
 
   /// <summary>
   /// Gets the current first page number..
   /// </summary>
   public Int32 FirstPageNumber
   {
      get => LastPageNumber - (NumberOfLinks - 1);
   }
 
   /// <summary>
   /// Gets a value indicating whether this instance has next page.
   /// </summary>
   public Boolean HasNextGroup
   {
      get => CurrentPage + NumberOfLinks < TotalPages;
   }
 
   /// <summary>
   /// Gets a value indicating whether this instance has previous page.
   /// </summary>
   public Boolean HasPreviousGroup
   {
      get => CurrentPage > NumberOfLinks;
   }
 
   /// <summary>
   /// Gets the last page number..
   /// </summary>
   public Int32 LastPageNumber
   {
      get
      {
         lastPageNumber = (Int32)Math.Ceiling((Double)CurrentPage / NumberOfLinks) * NumberOfLinks;
         if (lastPageNumber > TotalPages)
         {
            lastPageNumber = TotalPages;
         }
         return lastPageNumber;
      }
   }
 
   /// <summary>
   /// Gets or sets the number of links to show..
   /// </summary>
   public Int32 NumberOfLinks { getset; } = 10;
 
   /// <summary>
   /// Gets or sets the size of the page.....
   /// </summary>
   public System.Int32 PageSize { getset; }
 
   /// <summary>
   /// Gets or sets the Qty of rows skipped from the top of the select.....
   /// </summary>
   public System.Int32 Skip { getset; }
 
   /// <summary>
   /// Gets or sets the Qty of rows taken.....
   /// </summary>
   public System.Int32 Take { getset; }
 
   /// <summary>
   /// Gets or sets the amount of items to display.
   /// </summary>
   public System.Int32 TotalItems { getset; }
 
   /// <summary>
   /// Gets the total pages available to display..
   /// </summary>
 
   #endregion
 
}

The Data Class.

This class will contain your data and the IPagesInfo properties. In this sample, it will be called PersonsPager.

Note: You can easily create it by executing the stored procedure, copying the result and in a new code window (for example, an empty one created for PersonsPager), paste it by the Edit – Paste Special-Past JSON as Classes.

Then replace the names autogenerated by your own and change the type for the second one for the PagesInfo class.

The pasted code names the main class as Rootobject, which has been renamed to PersonsPager.

The Pager class will be changed by PagesInfo, and the Pager class defined could be removed.

This is the final code for the PersonsPager class.

public class PersonsPager
{
 
   public List[] List { getset; }
   public PagerInfo Pager { getset; }
}
public class List
{
   public int BusinessEntityID { getset; }
   public string FirstName { getset; }
   public string MiddleName { getset; }
   public string LastName { getset; }
}

The Pager Component.

You must add a Razor Component. In this sample, it is called Pager.razor.

In the UI code, I use an UL tag, adding a button for previous group of pages (when the user moves beyond the first set of page numbers), buttons for the different page numbers, and a button for the next group as well.

For the buttons, the pagination, page-item, and page-link classes are used.

<nav class="text-center">
   <ul class="pagination">
      @{//Previous page group button.
 
         string className = "page-item " + (!PagesInfo.HasPreviousGroup ? " disabled" : ""); //If there is in previous group, the button will be disabled
         <li class="@className">
            <button class="page-link "
                    @onclick="@(()=>
                                   {
                                      if (PagesInfo.HasPreviousGroup)
                                        ChangePage(PagesInfo.PreviousGroupStart);
                                   }
               )">
               ⏪
            </button>
         </li>
      }
      @*Buttons for page numbers*@
      @for (Int32 i = PagesInfo.FirstPageNumber; i <= PagesInfo.LastPageNumber; i++)
      {
         int pageSelector = i;
 
         className = "page-item " + (i == PagesInfo.CurrentPage ? " active" : "");
         <li class="@className">
            <button class="page-link" @onclick="@(()=>ChangePage(pageSelector))">@string.Format("{0:00}"pageSelector)</button>
         </li>
      }
      @{//Next page group button.
         className = "page-item  " + (!PagesInfo.HasNextGroup ? " disabled" : "");
         <li class="@className">
            <button class="page-link " @onclick="@(()=>
                                                      { if (PagesInfo.HasNextGroup)
                                                            ChangePage(PagesInfo.NextGroupStart);
                                                      }
               )">
               ⏩
            </button>
 
         </li>
      }
   </ul>
</nav>

In the code section, parameters are defined for:

  • an instance of a class implementing the IPagerInfo
  • a value to persist the selected page
  • an EventCallback to notify the client page about the changes in the selection by the user.

Finally, the ChangePage function called every time the user clicks ant of the buttons is defined to change the selected page and notify the client page.

@code {
   [Parameter]
   public IPagesInfo PagesInfo { getset; }
   [Parameter]
   public int SelectedPage { get => PagesInfo.CurrentPage; set => PagesInfo.CurrentPage = value; }
   [Parameter]
   public EventCallback OnPageChange { getset; }
   void ChangePage(int newPage)
   {
      PagesInfo.CurrentPage = newPage;
      SelectedPage = newPage;
      OnPageChange.InvokeAsync(SelectedPage);
   }
}

The controller.

To get the information from the database, you will need an API REST controller which returns the JSON string from the stored procedure.

The method must be decorated with the HttpGet attribute to react when the client page code calls it.

[Route("/[controller]")]
[ApiController]
public class PersonDataController : ControllerBase
{
   public PersonDataController(IConfiguration configuration)
   {
      Configuration = configuration;
   }
 
   public IConfiguration Configuration { get; }
 
   [HttpGet]
   public async Task<stringGetPersons(int selectedPageint pageSize = 10)
   {
      using SqlConnection con = new SqlConnection(Configuration.GetConnectionString("aw"));
      try
      {
         SqlCommand com = new SqlCommand("[Person].[Person_GetforPager]"con);
         com.CommandType = System.Data.CommandType.StoredProcedure;
         com.Parameters.AddWithValue("@skip", (selectedPage == 0 ? 0 : selectedPage - 1* pageSize);
         com.Parameters.AddWithValue("@take"pageSize);
         con.Open();
         string values = (await com.ExecuteScalarAsync()).ToString();
         return values;
      }
      catch (System.Exception ex)
      {
 
         throw;
      }
   }
}

The client page.

A new component will be defined to display the data.

It will contain any type of display for your data (in the sample, it is just a UL list), and an instance of the Pager component.

As usual in a Web assembly app, you must check if you have data to display before performing the UI generation.

Moreover, you can decide if you need display the pager component in case of no more than one page is needed to display the information.

@inject HttpClient httpClient
<h3>Persons</h3>
@if (result != null)
{
   <div>@result.StatusCode</div>
   <div>@result.Content.ReadAsStringAsync().Result</div>
   <div>@httpClient.BaseAddress</div>
}
@if (personsPager != null && personsPager.List.Count() > 0)
{
   <ul>
      @foreach (var item in personsPager.List)
      {
         <li>@item.LastName</li>
 
      }
   </ul>
   @if (personsPager.Pager != null && personsPager.Pager.TotalPages > 1)
   {
      <Pager PagesInfo="PagesInfo" OnPageChange="ChangePage" />
      @**@
   }
}
else SelectedPage = 1;

The code of this page will call the controller get method to retrieve the information.

@code {
   PersonsPager personsPager;
   int SelectedPage;
   public IPagesInfo PagesInfo { get => personsPager.Pager; }
   HttpResponseMessage result;
   protected async override Task OnInitializedAsync()
   {
      await GetDataAsync();
   }
   async void ChangePage()
   {
      SelectedPage = personsPager.Pager.CurrentPage;
      //SelectedPage = PagesInfo.CurrentPage;
      await GetDataAsync();
      this.StateHasChanged();
 
   }
   async Task GetDataAsync()
   {
      //result = await httpClient.GetAsync($"PersonData?Selectedpage={SelectedPage}");
      personsPager = await httpClient.GetFromJsonAsync<PersonsPager>($"PersonData?Selectedpage={SelectedPage}");
 
   }
 
}

And this is the result:

Persons page with Pager sample
Persons page with Pager sample

Note: The “aw” connection string points to the AdventureWorks2017 database where the stored procedure from the previous post has been created.

BTW here is the sample

Sponsored Post Learn from the experts: Create a successful blog with our brand new courseThe WordPress.com Blog

WordPress.com is excited to announce our newest offering: a course just for beginning bloggers where you’ll learn everything you need to know about blogging from the most trusted experts in the industry. We have helped millions of blogs get up and running, we know what works, and we want you to to know everything we know. This course provides all the fundamental skills and inspiration you need to get your blog started, an interactive community forum, and content updated annually.

Paging data, NOT client side.


It is frequent to make web sites, mobile, UWP or Windows Applications, which need to present information of several rows which must be presented in pages of data.

That could be accomplished by 3 basic methods:

  1. Send the content to the client and manage the pagination in it, for example, using some of the JScript frameworks available.
  2. Using some sort of LinQ sentence, against EF, which will get the group of data after retrieving the entire set from the database.
  3. Get the exact set of rows needed each time, by using T-SQL from SQL Server

The third method is the most responsive, since it processes the selection in the server side, and reduce the amount of traffic between server, application level and client UI.

This is how I get the information and paging support by using T-SQL.

The OFFSET – FETCH statements.

The basic tool for getting pages of data is the combination of OFFSET <n> FETCH <c>.

The n value is the number of rows excluded from the beginning of the result set, and the c value, the quantity to return.

Any query you create, could be modified by this pair of statements.

Note: This example, uses the AdventureWorks2017 sample database, which you can get here: https://docs.microsoft.com/en-us/sql/samples/adventureworks-install-configure

For example, let us get the employees form the database:

           SELECT
                  [BusinessEntityID],
                  [Title],
                  [FirstName],
                  [MiddleName],
                  [LastName]
              FROM
                   [Person].[Person]
              WHERE [PersonType] = N'EM'
              ORDER BY
                       [LastName],
                       [FirstName]
              OFFSET 10 ROWS FETCH NEXT 20 ROWS ONLY 

It is just a standard select, but with the addition of the OFFSET and FETCH NEXT statements.

Of course, the numeric values could be replaced by parameters, like in:

           SELECT
                  [BusinessEntityID],
                  [Title],
                  [FirstName],
                  [MiddleName],
                  [LastName]
              FROM
                   [Person].[Person]
              WHERE [PersonType] = N'EM'
              ORDER BY
                       [LastName],
                       [FirstName]
              OFFSET @Skip ROWS FETCH NEXT @Take ROWS ONLY 

In case you want to get the first set of rows, the offset value could be 0, and it will work perfectly for your need.

Getting information about the entire set of data.

If you want to display to the user, precise information about the total rows in the set of data etc. you just need to perform another query after this one, by using the same filter in WHERE statement, to count the rows.

At the same time, you can get the current page based in the offset and fetch values, like in this query:

SELECT
   COUNT(*) AS [TotalItems],
   @Skip / @Take + 1 AS [CurrentPage],
   @Take AS [PageSize]
     FROM
      [Person].[Person]
     WHERE [PersonType] = N'EM'

Combining both sentences in the same query, you get two result sets to obtain, for example, using a data wrapper like Dapper:

SELECT
       [BusinessEntityID],
       [Title],
       [FirstName],
       [MiddleName],
       [LastName]
   FROM
        [Person].[Person]
   WHERE [PersonType] = N'EM'
   ORDER BY
            [LastName],
            [FirstName]
   OFFSET @Skip ROWS FETCH NEXT @Take ROWS ONLY;

SELECT
       COUNT(*) AS [TotalItems],
       @Skip / @Take + 1 AS [CurrentPage],
       @Take AS [PageSize]
   FROM
        [Person].[Person]
   WHERE [PersonType] = N'EM';

A JSON for an API.

This sentence could be modified to retrieve a JSON structure containing the entire set, to transfer it by an API to any client, following these rules:

  • The entire set of information must be returned as a well-formed JSON.
  • Each part of the information must be property identified.
  • All the information must be returned as a single string value, which enables to get the information as a scalar value.
  • The first part must be identified as a list/array of data. The second one, just as a unique entry.

For the list of rows, adding FOR JSON PATH, will transform the result in a single JSON content.

For the information about the paging process, it will be modified to avoid create an array of JSON elements, by issuing FOR JSON PATH,WITHOUT_ARRAY_WRAPPER modifier.

Finally, concatenating both sets as different attributes of a single JSON structure, will do the trick.

To have it easy to use, we will make a stored procedure with the entire query, with the corresponding parameters, as follows:

CREATE PROCEDURE [Person].[Person_GetforPager]
 ( @skip INT = 0,
   @take INT = 20
 )
AS
      BEGIN
        SELECT
               '{"List":' +
            (
               SELECT
                      [BusinessEntityID],
                      [Title],
                      [FirstName],
                      [MiddleName],
                      [LastName]
                  FROM
                       [Person].[Person]
                  WHERE [PersonType] = N'EM'
                  ORDER BY
                           [LastName],
                           [FirstName]
                  OFFSET @Skip ROWS FETCH NEXT @Take ROWS ONLY FOR JSON PATH
            )
            + ',"Pager":' +
            (
               SELECT
                      COUNT(*) AS [TotalItems],
                      @Skip / @Take + 1 AS [CurrentPage],
                      @Take AS [PageSize]
                  FROM
                       [Person].[Person]
                  WHERE [PersonType] = N'EM' FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
            )
            + '}';  
    END; 

Executing the procedure, this is the result:

{
   {
      "List": [
         {
            "BusinessEntityID": 38,
            "FirstName": "Kim",
            "MiddleName": "B",
            "LastName": "Abercrombie"
         },
         {
            "BusinessEntityID": 211,
            "FirstName": "Hazem",
            "MiddleName": "E",
            "LastName": "Abolrous"
         },
         {
            "BusinessEntityID": 121,
            "FirstName": "Pilar",
            "MiddleName": "G",
            "LastName": "Ackerman"
         },
      ],
      "Pager": {
         "TotalItems": 273,
         "CurrentPage": 1,
         "PageSize": 10
      }
   }

In the next post, we will see how to use it form a Web Assembly application.

Cerdos y Relatividad


El título es un homenaje a mi padre, digno ejemplo de que cierto presidente latino americano está equivocado respecto a los méritos.

Mi padre decía “no mezclar chanchos (cerdos), con velocidad de la luz al cuadrado”, ante una pretensión de confundir cosas.

Y viene a colación porque, sin ser un científico de los datos, mayormente mi trabajo tiene que ver con ellos. No soy un especialista en el análisis, pero estoy día a día trabajando con ellos.

Pero me asombra un poco que tanto gobiernos como medios de comunicación y organizaciones variadas, lanzan datos respecto del lamentable COVID-19, sin darle el marco de referencia necesario.

Veamos algunos casos, con datos públicos obtenidos de https://ourworldindata.org/.

En el siguiente gráfico, vemos la curva de los casos por continente.

Gráfico que representa la pendiente de casos de COVID-19 poor continente, con el valor más altopara Norte América y el más bajo para Oceanía

Que el valor máximo se acerque a los 7 millones, es realmente de susto.

El problema es que desde un punto de vista de análisis de situación, el valor no dice mucho respecto de la gravedad de la enfermedad. Porque, no tiene marco de referencia.

Superpongamos el valor de Casos por millón de habitantes.

A tener en cuenta que la escala de la segunda curva, más oscura, es la de la derecha.

Este segundo valor es más representativo de la realidad, porque establece la proporcionalidad de afectación de la enfermedad.

Pero es que es mucho más dramático hablar de 7000000 que de 32000.

No se está diciendo que el problema. De hecho, todo lo contrario. Lo que se demuestra es que, a veces, los datos no son lo que parecen, depende de como se presenten.

Habiendo establecido esto, la comparativa de camas por mil habitantes, versus el total de casos por millón, nos permite considerar la capacidad de respuesta de cada región.

O visto por país en Sud América

O en Europa

Y así podríamos seguir.

Pero creo que está claro.

Como escuché alguna vez…

La estadística es lo que dice que todos comemos pollo y medio por mes.
Si este mes no comí ninguno, hubo un vivo que comió tres.

Hasta la próxima y a cuidarse.

Making Resources available for other .net dlls


The Problem

I was thinking in have a single common resources Dll for all my libraries to avoid confusion and centralize the localization of them.
But the VS Code Generator for the resource’s files insist in make the properties internal.
I need a way to make them public.

My Solution.

I used T4 template to build a class which exposes the same elements in the resource file as public. Doing so, I can expand the content any time, without the need to write more and more public members.

The basis.

The T4 template helps you creating a file (which could be a class), with the same name of the T4 file and the extension provided in the T4 file definition, using specific directives, like:

<#@ output extension=".cs" #>

(notice the syntax: which start with <#@ and ends with #> )
Inside it, you can write code to generate your class code, using WriteLine or in line tokens for that. (see the official documentation at https://docs.microsoft.com/en-us/visualstudio/modeling/code-generation-and-t4-text-templates ).

Some trick.

To build the different properties for your generated class, the T4 template needs to identify the resources created in the .resx file. As I already told you, they are internal and cannot be accessed from outside the dll. The T4 generator is, in fact, outside the project.
However, the text transformation process (the “generator”), is capable to use Reflection to reach public members like any other .Net code. We just a need to query the resource members rom outside… and that could be done by creating a public class to which we can call and retrieve the members.
At the same time, we can use it to have other practical methods for reach our resources.
Using the partial class feature, we can define the code we need in a partial class which could be part of the generated one.
Let consider we will generate a “Resources.cs” class. We need another file, with some kind of extended extension (I use this name to define a file with more than one dot in its name).
In this case, we have to add a class named, as example, Resources.Tools.cs where we build a public function capable to get the members names of the resource file,. Something like this code:

public partial class Resources
{
   #region "Generator helper"
   public static List<PropertyInfoGetResourcesMembers()
   {
      List<PropertyInfotoGen = new List<PropertyInfo>();
      var res = (from TypeInfo item in System.Reflection.Assembly.GetExecutingAssembly().GetTypes() where item.IsNotPublic orderby item.Name select item.AsType()).ToList();
      foreach (var item in res)
      {
         toGen.AddRange(item.GetProperties(BindingFlags.NonPublic | BindingFlags.Static).OrderBy(x => x.Name).Where(x => x.Name != "ResourceManager"));
      }
      return toGen;
   }
   #endregion "Generator helper"
}

The function analyzes the classes Not Public in the project dll and retrieves the list of properties for all of them.
Once we have this dll compiled, we can proceed to create our T4 template, adding a file named Resources.tt (the tt is the extension for T4 templates).
I’ll no go into the details of all the T4 syntax but, believe me, you need this list of references and directives:

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Reflection" #>
<#@ assembly name="System.Xml.Linq" #>
<#@ assembly name="System.Xml" #>
<#@ assembly name="EnvDTE" #>
<#@ assembly name="EnvDTE80" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #> 
<#@ import namespace="EnvDTE" #>
<#@ import namespace="EnvDTE80" #>
<#@import namespace="Microsoft.VisualStudio.TextTemplating" #>
<#@import namespace="System.IO" #>
<#@import namespace="System.Xml.Linq"#> 
<#@ output extension=".cs" #>

And here you have the entire explained code:

Snippet

<#
    // retrieve the Visual studio Environment
    DTE dte = ((IServiceProvider)(this.Host)).GetCOMService(typeof(DTE)) as DTE;
    //  Get the projects in current solution
    IList<Project> Projects=GetProjects(dte);
    string debugPath="";
    // define thew name of the class to build (and the partial class we have already created with our helper function)
    string className=Path.GetFileName(this.Host.TemplateFile).Replace(".tt","");
    // Get the directory where the template is stored
    DirectoryInfo projectDir = new DirectoryInfo( Path.GetDirectoryName(this.Host.TemplateFile));
    // Obtain the project file itself
    FileInfo projectFile=( from el in projectDir.GetFiles("*.csproj"select  el).FirstOrDefault();
    // Cleans the project name to search it into the projects' list of the solution
    string projectName=projectFile.Name.Replace(".csproj","");
    // Get the Project object
    Project resourcesPrj=(from el in Projects where el.Name==projectName select el).FirstOrDefault();
    // Gets the directory where the project is stored
    string filePath=(from Property prop in resourcesPrj.Properties where prop.Name=="LocalPath" select prop.Value.ToString()).FirstOrDefault();
    // Obtains the Default name space of the project
    string namespaceName=(from Property prop in resourcesPrj.Properties where prop.Name=="DefaultNamespace" select prop.Value.ToString()).FirstOrDefault();
    // obtains the file name of the compiled the binaries
    string outputFile=(from Property prop in resourcesPrj.Properties where prop.Name=="OutputFileName" select prop.Value.ToString()).FirstOrDefault();
    // Reads the project fiel as XML
    XElement xel = XElement.Load(resourcesPrj.FullName);
    // Find the TargetFramewroks entry to see it it is netStandard or not (the binary is stored in different path)
    XElement fwk = xel.Descendants("TargetFrameworks").FirstOrDefault();
    // Defines the destination folder for the debug binaries depending on the value of TargetFrameworks
    if (fwk !=null && fwk.Value.Contains("netstandard2"))
    {
        debugPath = @"\bin\Debug\netstandard2.0\";

    }
    else
    {
        debugPath = @"\bin\Debug\";;
    }
    // Configure the full path for the binaries
    filePath+=debugPath+outputFile;
    // Loads the latest version of the binaries as an assembly (to get the up to date list, you will need to compile once BEFORE generate the class with this template)
    System.Reflection.Assembly ass=System.Reflection.Assembly.Load(System.IO.File.ReadAllBytes(filePath));
    try
    {
        // Get the type already created with the same name than this template (the first time, it will contains ONLY the support function)
        Type tr = ass.GetType(namespaceName + "." + className);
        // Calls the support method we created, to get the list of the entire resources
        List<System.Reflection.PropertyInfo> toGen = (List<System.Reflection.PropertyInfo>)tr.GetMethod("GetResourcesMembers").Invoke(nullnull);
        if (toGen.Count > 0)
        {
            // start writin g the result file, stariting by its namespace
            WriteLine("namespace " + namespaceName);
            WriteLine("{");
            // define the class (as partial to be combined with the .Tools.cs file during the build of the dll)
            WriteLine("public static partial class " + className);
            WriteLine("{");
            Type prevType = null// this variable store from which type are we getting resources names. THe first time will be null, and will be defined in the following lines
            foreach (System.Reflection.PropertyInfo item in toGen)
            {
                if (prevType != item.DeclaringType) // DeclaringType contains the type where this PropertyInfo belongs to
                {
                    if (prevType != null)// in case we already have a previous type, we ar in a change of type, so we need to close the current generated class
                    {
                        WriteLine("}");
                    }
                    prevType = item.DeclaringType; //<= HERE we store the type from witch we are getting members
                    WriteLine("public static class " + prevType.Name + "{"); // defines a nested class with the DeclaringType name

                }
                WriteLine(string.Format("public static {0} {1} => {3}.{2}.{1};", item.PropertyType, item.Name, item.DeclaringType.Name, namespaceName)); // writes the public declaration of the property, which internally calls the internal member
            }
            // closes the final curly brackets
            WriteLine("}");
            WriteLine("}");
            WriteLine("}");
        }
    }


    catch (Exception ex)    {
        var ufa = ex.Message;
    }
    finally
    {
        ;


    }
        
        
#>
    <#+
    // NOTICE: The syntax for functions includes a plus sign
    // It is hard to get the projects inside a solution, since there could be folders etc. in the solution.
    // See https://wwwlicious.com/envdte-getting-all-projects-html/ where I get this part of the code.(Thanks Scott)
    public  IList<Project> GetProjects(DTE dte)
    {
        Projects projects = dte.Solution.Projects;
        List<Project> list = new List<Project>();
        var item = projects.GetEnumerator();
        while (item.MoveNext())
        {
            var project = item.Current as Project;
            if (project == null)
            {
                continue;
            }
    
            if (project.Kind == ProjectKinds.vsProjectKindSolutionFolder)
            {
                list.AddRange(GetSolutionFolderProjects(project));
            }
            else
            {
                list.Add(project);
            }
        }

        return list;
    }

    private  IEnumerable<Project> GetSolutionFolderProjects(Project solutionFolder)
    {
        List<Project> list = new List<Project>();
        for (var i = 1; i <= solutionFolder.ProjectItems.Count; i++)
        {
            var subProject = solutionFolder.ProjectItems.Item(i).SubProject;
            if (subProject == null)
            {
                continue;
            }

            // If this is another solution folder, do a recursive call, otherwise add
            if (subProject.Kind == ProjectKinds.vsProjectKindSolutionFolder)
            {
                list.AddRange(GetSolutionFolderProjects(subProject));
            }
            else
            {
                list.Add(subProject);
            }
        }
        return list;
    }
#>

A final note

It is not so easy to get the projects inside the solution, since there could be a mess of folders, and other kind of files defined in a .sln file.
I want to thanks Scott Mackay for publish this useful entry: https://wwwlicious.com/envdte-getting-all-projects-html/

1, 2, 3… Probando


Cuando hacemos bibliotecas en general, resulta muy difícil poder abarcar todas las posibilidades, por mucho que pensemos, y analicemos.

Sin ir más lejos, esta última semana me encontré con que tenía que hacer algunos cambios en nuestra biblioteca common, para facilitar serializar (almacenar binariamente, en este caso, ya que serializando a XML no necesitaba modificar nada). Pero de eso hablaremos después.

El punto es que, tarde o temprano, modificaremos nuestro código, lo cual no está para nada mal… se llama evolución J

Esto nos lleva a la necesidad tener previstos mecanismos para comprobar que los cambios no afecten las funcionalidades que ya teníamos aseguradas y probadas.

Cuanto más evolucionemos nuestras bibliotecas, más complejos serán los pasos para volver a probar que todo funcione como esperamos.

Es por ello que existen los proyectos de tipo prueba, que incluye no solo esta opción (probar bibliotecas) sino que abarca pruebas de interfaz, de rendimiento, etc.

Pero, hoy, hablaremos de pruebas para nuestras funciones, o Pruebas Unitarias

Es de considerar que un mismo proyecto de pruebas, podrá aplicarse a múltiples bibliotecas, e inclusive hasta es conveniente, dado que nos permitirá automatizar pruebas múltiples en el tiempo.

Procedamos, pues, a agregar un proyecto de pruebas.

El proyecto expone contiene clases de pruebas unitarias, marcadas como TestClass (por atributo).

Además, cada método que debe cumplir una prueba, se decora con el atributo TestMethod.

¿Y por qué es esto?

Sencillamente, porque un motor de pruebas, embebido en Visual Studio, reconoce dicha clases y métodos, para ejecutarlos cada vez que se requiera hacer las pruebas.

Team Foundation Server (Y Visual Studio Online), poseen motores de pruebas parecidos.

 

Anunciar si la prueba fue satisfactoria o no.

Cada método de prueba es un procedimiento, que hace lo necesario para llamar una función (o inclusive más).

Dicho método debe reportar al motor de pruebas si ésta fue satisfactoria. Y esto se hace utilizando Assert (Afirmar, aseverar), que dispone de varios métodos de respuesta lógica, como “Es Igual”, “Es Distinto”, etc.

Así el motor sabe, cuando las pruebas se realizan en modo automatizado, el resultado en cada caso.

Veamos por ejemplo, una prueba unitaria de nuestra biblioteca de recursos.

[TestClass]
public class Resources
{
    [TestMethod]
    public void GetString()
    {
        string result = DS.Resources.Resources.ErrorTitle;
        string lang = System.Threading.Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName;
        Assert.AreEqual(result, lang==“en” ? “Operation error”“Error en la operación”);
    }
}

 

Finamente, podemos probar individualmente un método, e inclusive depurar el proceso de prueba, en el entorno de Visual Studio, con el menú contextual que aparece en cualquiera de los métodos marcados como de prueba.

Evaluando resultados

Al ejecutarse las mismas, el Explorador de Prueba, en Visual Studio, nos presenta los resultados.

En la imagen se muestran las pruebas de varios métodos de todas las bibliotecas de las que hemos hablado.

 

 

 

Generalizando recursos.


Una de las características importantes de las aplicaciones en el mundo globalizado es la necesidad de utilizar recursos para poder localizar mensajes, textos y representaciones gráficas acordes a cada posible usuario.

Esto incluye, como es lógico, cualquier comunicación que parta desde nuestras bibliotecas.

Sin embargo, considerando a futuro, el agregado de idiomas, resulta bastante engorroso mantener archivos de recursos en cada biblioteca que definamos.

¿Por qué no tener centralizados los mismos en una sola biblioteca?

Las herramientas que nos permiten manejar archivos de recursos en nuestros proyectos, generan clases que exponen dichos recursos pero que no se exponen a otras bibliotecas (no son públicas).

Lo primero que se nos ocurriría sería agregarles el modificador public, pero, el generador de Visual Studio re crea la clase completa ante cada cambio y, por tanto, deberíamos ir a modificarla en cada ocasión. L

Nos hace falta otro mecanismo que mantenga una clase, pública, que exponga todos nuestros recursos aun cuando agreguemos o eliminemos algunos.

Y para ello, nos podemos apoyar en la transformación de Texto.

Transformación de Texto (T4).

T4 aparece en Visual Studio 2010 y es en lo que se basó la generación de clases a partir del modelo de datos de una base SQL Server, en Entity Framework.

(del tema de EF… mejor hablamos en otra ocasión) L

El concepto es simple: tienes una plantilla que se usa para generar código (o páginas HTML, o texto plano), la cual puedes usar para reconstruir clases, o generar textos basados en datos (como reportes sencillos), o cosas asi.

En este caso, la idea es simple: crear una clase que exponga como público un recurso, y utilizarlo luego como plantilla para, recorriendo los miembros definidos en un archivo de recursos re exponerlos como públicos.

Los recursos.

Para mantener orden en nuestro desarrollo, definamos una nueva biblioteca de clases, dentro de la cual, definimos un archivo de recursos con un nombre específico para poder identificar la clase generada fácilmente.

   

El espacio de nombres será DS.Resources, y el archivo de recursos (y su clase derivada) CommonResources.

Una interesante herramienta de manejo de recursos.

Entre todos los componentes que podemos encontrar como extensión, tenemos una que facilita la edición de los recursos para múltiples idiomas.

Además de permitir definir los mismos en más de un idioma (y agregar idiomas a posteriori), facilita la exportación e importación de los mismos, para obtener traducciones desde profesionales del tema.

La clase plantilla

Esta clase que usaremos como ejemplo, expone una de los recursos contenidos en el archivo CommonResources desde una clase pública.

Public Class Resources
Public shared ReadOnly Property Copyright  As String
    Get
        Return My.Resources.CommonResources.Copyright
    End Get
End Property
End Class

 

 

using DS.Resources;
using System;
namespace DS.Resources
{
    public static class Resources
    {
        public static String Copyright { get 
            { return CommonResources.Copyright; } }
    }
}

 

La transformación de texto.

Agregamos ahora a nuestro proyecto, un archivo de tipo Plantilla de Texto

(Existen dos tipos de plantilla de texto. Hay una específica para que sea utilizada por tu proyecto en tiempo de ejecución. En este caso, no es esa).

El nombre que le des a ese archivo, será el que tendrá la clase luego. Por tanto, a este archivo le llamamos Resources.

Pegamos el código de la clase en este archivo, que utilizaremos para reemplazar los valores de nombre de la propiedad y tipo de datos, por los que recuperaremos de los definidos en la clase CommonResources

 

Public Class Resources
Public shared ReadOnly Property <#= sName #>  As <#= sType #>
    Get
        Return My.Resources.CommonResources.<#= prop.Name #>
    End Get
End Property
End Class

 

Explicando, los elementos encerrados entre <# y #> son código ejecutable en tiempo de transformación, mientras que el resto, se toman literalmente.

Utilizando Reflection (si, de nuevo), y el modelo de objetos de Visual Studio, podemos obtener los elementos definidos en el archivo de recursos, e incluirlos en un bucle con este código, para generar cada una de las propiedades públicas.

Este sería el código completo de la plantilla.

<#@ template debug="true" hostspecific="true" language="VB" #>
<# ' Define which assemblies must be imported #>
<#@ assembly name="System.Core" #>
<#@ assembly name="EnvDTE" #>  
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".vb" #>
Public Class Resources
<#
    dim serviceProvider as IServiceProvider= Me.Host  
    dim dte as EnvDTE.DTE = serviceProvider.GetService(gettype(EnvDTE.DTE)) 
    dim item = dte.Solution.FindProjectItem(Host.TemplateFile).ContainingProject
    dim OutPutFile as string= item.Properties("FullPath").value & "bin\debug\" & item.Properties("OutputFileName").Value
    Dim ass As System.Reflection.Assembly = System.Reflection.Assembly.LoadFrom(OutPutFile)
    Dim t As Type = ass.GetType("DS.Resources.My.Resources.CommonResources")
    Dim props = (From el In DirectCast(t, System.Reflection.TypeInfo).DeclaredProperties Where Not {"ResourceManager", "Culture"}.Contains(el.Name)).ToList
    For Each prop As System.Reflection.PropertyInfo In props
    dim sName as string=prop.Name
    dim sType as string=prop.PropertyType.Name
#>
Public shared ReadOnly Property <#= sName #>  As <#= sType #>
    Get
        Return My.Resources.CommonResources.<#= prop.Name #>
    End Get
End Property
<#
    Next
    ass=nothing
#>

End Class

<#@ template debug="true" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="EnvDTE" #>  
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>

using DS.Resources;
using System;

namespace DS.Resources
{
   public static class Resources
    {
    <#
    IServiceProvider serviceProvider = (IServiceProvider)this.Host;  
    EnvDTE.DTE dte = (EnvDTE.DTE) serviceProvider.GetService(typeof(EnvDTE.DTE));  
    var item = dte.Solution.FindProjectItem(Host.TemplateFile).ContainingProject;
    string OutPutFile = item.Properties.Item("FullPath").Value + "bin\\debug\\" + item.Properties.Item("OutputFileName").Value;
    System.Reflection.Assembly ass   = System.Reflection.Assembly.LoadFrom(OutPutFile);
            Type t = ass.GetType("DS.Resources.CommonResources");
    System.Reflection.TypeInfo tInfo=(System.Reflection.TypeInfo)t;
            string[] noAdd = new string[] { "ResourceManager", "Culture" };
    var props = (from el in ((System.Reflection.TypeInfo)t).DeclaredProperties where !noAdd.Contains(el.Name) select el).ToList();
    foreach( System.Reflection.PropertyInfo prop in props)
    {
        string sName =prop.Name;
        string sType =prop.PropertyType.Name;
    #>
            public static <#= sType #> <#= sName #> { get { return CommonResources.<#= sName #>; } }
    <#
    }
    #>
    }
}

 

Algunas aclaraciones de la plantilla.

<#@ template debug=”true” hostspecific=”true” language=”xx” #>
Determina que se trata de una plantilla, a la cual
podemos querer depurar, y que es específica para el “host” (en este caso, el entorno de Visual Studio. En esta plantilla en particular es importante, ya que necesitamos obtener información del proyecto donde se encuentra la plantilla. En forma predeterminada este valor viene en falso, y por tanto, nos daría un error al tratar de acceder a información del proyecto, como veremos luego.

<#@ assembly name=”EnvDTE” #>  

Necesitamos incluir este ensamblado, dado que vamos a obtener información del entorno de desarrollo (ese ensamblado aplica precisamente, a comunicarse con el propio entorno de Visual Studio).

dim serviceProvider as IServiceProvider= Me.Host  
dim dte as EnvDTE.DTE = serviceProvider.GetService(gettype(EnvDTE.DTE)) 

Obtiene la instancia actual del entorno de desarrollo de Visual Studio.

dim item = dte.Solution.FindProjectItem(Host.TemplateFile).ContainingProject

dim OutPutFile as string= item.Properties(“FullPath”).value & “bin\debug\” & item.Properties(“OutputFileName”).Value

Obtiene el Proyecto actual y de este, genera en la variable OutPutFile, el camino completo a la DLL resultante de la compilación del proyecto.

Dim ass As System.Reflection.Assembly = System.Reflection.Assembly.LoadFrom(OutPutFile)
Dim t As Type = ass.GetType(“DS.Resources.My.Resources.CommonResources”)

Cargamos por reflexión dicha dll, y obtenemos la definición de la clase CommonResources, que es la que contiene nuestros recursos.

Dim props = (From el In DirectCast(t, System.Reflection.TypeInfo).DeclaredProperties Where Not {“ResourceManager”, “Culture”}.Contains(el.Name)).ToList

En la variable Props, obtenemos todas las propiedades definidas en dicha clase, excluyendo, las dos que siempre se definen en los archivos de recursos: ResourceManager y Culture, dado que no son definidas por nosotros.

Luego, sencillamente, recorremos esa lista, y generamos las distintas propiedades de la clase pública.

Al grabar la plantilla, el entorno transformará el mismo generando la clase.

Nota: por estar generando código dinámicamente, el entorno alerta de dicha situación, por temas de seguridad


Quedando el resultado como el siguiente ejemplo.

 

Public Class Resources
    Public Shared ReadOnly Property Copyright As String
        Get
            Return My.Resources.CommonResources.Copyright
        End Get
    End Property
    Public Shared ReadOnly Property ErrorTitle As String
        Get
            Return My.Resources.CommonResources.ErrorTitle
        End Get
    End Property
    Public Shared ReadOnly Property FKError As String
        Get
            Return My.Resources.CommonResources.FKError
        End Get
    End Property
    Public Shared ReadOnly Property NameValueList_MissingType As String
        Get
            Return My.Resources.CommonResources.NameValueList_MissingType
        End Get
    End Property
    Public Shared ReadOnly Property NotAllowedBySystem As String
        Get
            Return My.Resources.CommonResources.NotAllowedBySystem
        End Get
    End Property
End Class

using System;

namespace DS.Resources
{
   public static class Resources
    {    public static String Copyright { get { return CommonResources.Copyright; } }    public static String ErrorTitle { get { return CommonResources.ErrorTitle; } }    public static String FKError { get { return CommonResources.FKError; } }    public static String NameValueList_MissingType { get { return CommonResources.NameValueList_MissingType; } }    public static String NotAllowedBySystem { get { return CommonResources.NotAllowedBySystem; } }
        }
}

 

Un detalle: si revisas el código resultante, verás que no es similar. Esto es porque en los dos lenguajes, la ubicación por espacio de nombres es diferente. En VB, los recursos se incluyen dentro del espacio de nombres especial My, que se refiere al proyecto completo, que en C# no existe.

Pero, para los objetivos buscados, son idénticos.

Hasta la próxima, donde probaremos estas cosas. (Adecuadamente)

 

Bibliotecas Comunes y Herramientas


Bien, como fue comentado en la publicación anterior, vamos a comenzar a crear nuestra propia biblioteca de herramientas y elementos de soporte (a algunos les ha dado por llamarlo “Framework” ).

Aprovechando las ventajas de diseño de Visual Studio (por cierto, estoy usando VS2017), el gráfico representa el esquema base de la solución.

Este esquema lo iremos ampliando con otras bibliotecas a futuro. (Por cierto, son bibliotecas, aunque la mayoría les digan librerías… que librerías son las que venden libros ).

Para facilitar la comprensión del código, la misma solución está desarrollada en paralelo, en C# y en VB. Lo interesante es que son perfectamente intercambiables (y mezclables, puedes usar una dll de un idioma y otra de otro, y funcionarán perfectamente).

Common.

Primero lo primero. O sea, elementos comunes que podrán ser utilizados por cualesquiera de nuestras bibliotecas


En esta biblioteca incluimos toda la implementación de los valores con nombre, a saber:

  • INameValue
  • NameValue<T>
  • NameValueList

Agregamos algunos atributos que seguramente usaremos a futuro (para implementar, por ejemplo, modelos de datos)

  • DataFieldAttribute
  • DataKeyAttribute
  • DataRelationAttribute
  • DefaultValueEnum

Y una interfaz que nos permitirá luego diferenciar errores cuando sean propios del sistema (excepciones), o los que implementen esta interfaz, aquellos que deben ser informados al usuario.

  • IUserMessageError

Tools

Como segundo proyecto de nuestro entorno de trabajo, creamos la biblioteca de Herramientas, que nos será útil para muchas acciones comunes a cualquier proyecto. En ella, que irá evolucionando seguramente, definiremos aquellos métodos que nos serán útiles repetidas veces.

Lo importante es, darse cuenta a tiempo, cuándo una función que escribimos, podría ser útil en otro caso .


Tenemos en esta biblioteca, las siguientes clases:

  • Reflection: de la que hablamos hace unas publicaciones atrás, que nos permite evaluar características de nuestras propias clases.
  • Logger: como su nombre indica, esta clase permitirá escribir en un log, sea este un archivo físico o el de Aplicación del Sistema Operativo.
  • Interaction: Este es un caso particular… me encontré en más de una ocasión, que cierta funcionalidad implementada en uno de los lenguajes de programación, no tenía correspondencia en el otro. Raro, pero a veces pasa. Como ejemplo, una de las funciones útiles en VB que no existe en C#. CallByName y que en esta clase, podemos implementar. Para mantener coherencia entre distintas versiones de bibliotecas, aun cuando en VB esa función existe como nativa, la mantenemos implementada en esta biblioteca también.

Queda aquí como ejemplo, ambas versiones de esta función, para que quede claro el concepto. Por lo demás, el código está perfectamente comentado. (en inglés, para que sirva a cualquiera que lo desee utilizar, sin barreras de idioma).

En la próxima, una biblioteca que plantea algún desafío interesante, y una herramienta de Visual Studio que nos permite subsanarlo… y la publicación del conjunto completo de código.


public static object CallByName(
    object ObjectRef,
    string ProcName,
    CallType UseCallType,
    params object[] Args
    )
{
    switch (UseCallType)
    {
        case CallType.Method:
            MethodInfo m = ObjectRef.GetType().GetMethod(ProcName);
            return m.Invoke(ObjectRef, Args);
        case CallType.Get:
            PropertyInfo p = ObjectRef.GetType().GetProperty(ProcName);
            return p.GetValue(ObjectRef, Args);
        case CallType.Let:
        case CallType.Set:
            {
                PropertyInfo pL = ObjectRef.GetType().GetProperty(ProcName);
                if (Args == null)
                {
                    pL.SetValue(ObjectRef, null);

                }
                else
                {
                    pL.SetValue(ObjectRef, Args[0]);
                }
                return null;
            }
    }
    throw new ArgumentException(string.Format("Invalid CallType {0}", UseCallType));
}


Public Shared Function CallByName(
                                 ObjectRef As Object,
                                 ProcName As String,
                                 UseCallType As CallType,
                                 ParamArray Args As Object()
                                 ) As Object
    Return Microsoft.VisualBasic.CallByName(
                        ObjectRef,
                        ProcName,
                        UseCallType,
                        Args
                    )
End Function

 

El blog de Dani Seara

Microsoft Azure Blog

El blog de Dani Seara

VIVIENDO RODANDO

de rodar con cámara a rodar en una silla

Matías Iacono

Coding stuff

Leandro Tuttini Blog

El blog de Dani Seara

WindowServer

El blog de los paso a paso

campusMVP.es

El blog de Dani Seara

Angel \"Java\" Lopez on Blog

Software Development, in the Third Millenium

Angel "Java" Lopez

El blog de Dani Seara

Atascado en los 70 II (El regreso)

Segunda época del rockblog "Atascado en los 70". VIEJAS canciones y artistas PASADOS DE MODA. Tratamos al lector de usted y escribimos "rocanrol" y "roquero" con ortografía castellana.

Geeks.ms

El blog de Dani Seara

El Bruno

Innovation Dude

Cajon desastre

Just another WordPress.com site

Pasión por la tecnología...

Todo sobre tecnología Microsoft en general, y Office 365 y SharePoint en partícular...

return(GiS);

Mi sitio geek