.NET Core MVC Web API – control DateTime format in JSON output using JsonSerializerOptions

When creating API’s with .NET Core MVC, you can control the JSON output by adding JsonOptions to the controllers:

public void ConfigureServices(IServiceCollection services)
{
  ...
  ...
  services.AddControllers().AddJsonOptions();
  ...
  ...
}

This will ensure that when requesting application/json from a GET method, the format returned is JSON.

You can then add Converters to the configuration, controlling the default behavior of the JSON. This is a DateTime converter, forcing any DateTime type to be outputted as “2019-15-09Y22:30:00

using System.Text.Json;
using System.Text.Json.Serialization;

namespace MyCode.Converters
{
  public class DateTimeConverter : JsonConverter<DateTime>
  {
    public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
      return DateTime.Parse(reader.GetString());
    }

    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
    {
      writer.WriteStringValue(value.ToLocalTime().ToString("yyyy-MM-ddTHH:mm:ss"));
    }
  }
}

The converter needs to be added to my configuration:

public void ConfigureServices(IServiceCollection services)
{
  ...
  ...
  services.AddControllers().AddJsonOptions(options =>
    {
      options.JsonSerializerOptions.Converters.Add(new Converters.DateTimeConverter());
    }
  );
  ...
  ...
}

MORE TO READ:

Advertisements
Posted in .net, .NET Core, c# | Tagged , , , , | 1 Comment

Sitecore Publish Items using the PublishManager

Sitecore have two entries to publishing:

You should use the PublishManager. Sitecore introduced the Publishing Service which is an optional external publisher that is much faster than the built in publishing. It comes with a small price as there are differences to integration points, and publishing behavior. But if you use the PublishManager you will be ready to switch in case you need the improved performance and are willing to manage yet another external service.

Using the Publisher class will only publish through the built in publisher.

The PublishManager is very simple to use:

using Sitecore.Data.Items;
using System;
using Sitecore.Data;
using Sitecore.Publishing;

PublishOptions publishOptions = new PublishOptions(item.Database, Database.GetDatabase("web"), publishMode, item.Language, DateTime.Now);
publishOptions.RootItem = item;
publishOptions.Deep = deepPublish;
publishOptions.PublishRelatedItems = publishRelatedItems;
publishOptions.CompareRevisions = compareRevisions;

var handle = PublishManager.Publish(new PublishOptions[] { publishOptions });
PublishManager.WaitFor(handle);

The WaitFor() call is only needed if you wish to wait for the publish to end before continuing.

You can create a simple extension method that can publish an item like this:

using Sitecore.Data.Items;
using System;
using System.Globalization;
using Sitecore.Data;
using Sitecore.Diagnostics;
using Sitecore.Publishing;

namespace MyCode
{
  public static class ItemExtensions
  {
    public static void PublishItem(this Item item, PublishMode publishMode, bool publishAsync = false, bool deepPublish = false, bool publishRelatedItems = false, bool compareRevisions = false)
    {
      
      if (item == null)
        return;

      PublishOptions publishOptions = new PublishOptions(item.Database, Database.GetDatabase("web"), publishMode, item.Language, DateTime.Now);
      publishOptions.RootItem = item;
      publishOptions.Deep = deepPublish;
      publishOptions.PublishRelatedItems = publishRelatedItems;
      publishOptions.CompareRevisions = compareRevisions;

      var handle = PublishManager.Publish(new PublishOptions[] { publishOptions });
      if (publishAsync)
        return;
      PublishManager.WaitFor(handle);
    }
  }
}

And you can use the extension method like this:

Item dbItem = Context.ContentDatabase.GetItem(xxx,xxx,xxx);
dbItem.PublishItem(PublishMode.SingleItem, false, false, false, false);

Thanks to Stephen Pope for reminding me of the existence of the PublishingManager class.

MORE TO READ:

Posted in .net, c#, General .NET, Sitecore 8, Sitecore 9 | Tagged , , | 2 Comments

Sitecore publish certain items on save

My Sitecore solution have some items, where changes to certain fields are so critical that they need to be published immediately. So when my user presses the Save button, I will open a popup dialog, asking if I should publish the change:

Catalog Changed

Catalog Changed

If you click OK, the item is published.

EXTENDING THE SAVEUI PIPELINE

To achieve this, you have several tools in the Sitecore toolbox. One is to extend the SaveUI pipeline. This operation requires me to hook in just after the Save processor, as we need to publish the saved changes:

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:env="http://www.sitecore.net/xmlconfig/env/">
  <sitecore>
    <processors>
      <saveUI>
        <processor patch:after="processor[@type='Sitecore.Pipelines.Save.Save, Sitecore.Kernel']" type="MyCode.MyProcessor, MyDll" />
      </saveUI>
    </processors>
  </sitecore>
</configuration>

CREATING A SAVEUI PROCESSOR

The processor consists of 2 parts: One that checks if the template have been saved and shows the dialog box, and another that publishes the item if the user clicks yes in the box:

using System;
using Sitecore;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Publishing;

namespace MyCode
{
  public class Myprocessor
  {
    // If this template is changed we request a publish
    private static ID _TEMPLATE_ID = new ID("{C24F3426-F599-482E-BBAA-A50645AD3304}");

    public void Process(Sitecore.Pipelines.Save.SaveArgs args)
    {
      Assert.ArgumentNotNull(args, "args");

      if (!args.HasSheerUI)
      {
        return;
      }

      // This piece of code is executed when the user clicks
      // on yes or no in the dialog box
      if (args.IsPostBack)
      {
        Context.ClientPage.Modified = false;
        if (args.Result == "yes")
        {
          foreach (Sitecore.Pipelines.Save.SaveArgs.SaveItem uiItem in args.Items)
          {
            Item dbItem = Context.ContentDatabase.GetItem(uiItem.ID, uiItem.Language, uiItem.Version);
            // PublishItem is an extension metod. I'll explain this one later
            dbItem.PublishItem(PublishMode.SingleItem, true, false, false, false);
          }
        }
        return;
      }

      Assert.IsNotNull(Context.ContentDatabase, "Sitecore.Context.ContentDatbabase");
      foreach (Sitecore.Pipelines.Save.SaveArgs.SaveItem uiItem in args.Items)
      {
        // This code checks if it is the template in question that have been saved
        Item dbItem = Context.ContentDatabase.GetItem(uiItem.ID, uiItem.Language, uiItem.Version);
        if (dbItem.TemplateID == _TEMPLATE_ID)
        {
          // Yes, the template have been saved, but do not ask for a publish
          // if the item is not publishable
          if (!dbItem.Publishing.IsPublishable(DateTime.Now, false))
            return;
          // Show the popup dialog and wait for the respone
          Sitecore.Web.UI.Sheer.SheerResponse.Confirm($"You have changed this catalog. You need to publish it. Would you like to publish now?");
          args.WaitForPostBack();
          return;
        }
      }
    }
  }
}

PUBLISH ITEM USING AN EXTENSION METHOD:

UPDATE 2019-09-13: There is another way of publishing. Read Sitecore Publish Items using the PublishManager for an improved way of publishing.

To do the publish I created this Item extension method:

namespace MyNamespace
{
  public static class ItemExtensions
  {
    public static void PublishItem(this Item item, PublishMode publishMode, bool publishAsync = false, bool deepPublish = false, bool publishRelatedItems = false, bool compareRevisions = false)
    {
      if (item == null)
        return;

      PublishOptions publishOptions = new PublishOptions(item.Database, Database.GetDatabase("web"), publishMode, item.Language, DateTime.Now);
      publishOptions.RootItem = item;
      publishOptions.Deep = deepPublish;
      publishOptions.PublishRelatedItems = publishRelatedItems;
      publishOptions.CompareRevisions = compareRevisions;

      Publisher publisher = new Publisher(publishOptions);
      if(publishAsync)
        publisher.PublishAsync();
      else
        publisher .Publish();
    }
  }
}

FINAL NOTE: BEWARE OF THE CTRL+S BUG IN SITECORE

Sitecore have a bug where the save event is called twice when clicking CTRL+S instead of the [Save] button. This can mess up the SheerResponse and can lead to your checkboxes and dropdowns to be emptied. The bug is easy to fix:

Sitecore CTRL+S fires item:saving event twice

MORE TO READ:

 

 

Posted in .net, c#, Sitecore 6, Sitecore 7, Sitecore 8, Sitecore 9 | Tagged , , | 1 Comment

ID or Id? Naming conventions in code

This topic is not as toxic than the tabs-vs-spaces or 2 vs 4 indents discussion, but it’s still something every coder I have met have an opinion about, no matter the programming language:

Which is correct:

  • OrderId
  • OrderID

Lets solve this ancient mystery like any programming problem is solved: We Google it.

THE MERRIAM-WEBSTER DICTIONARY

According to Merriam-Webster, ID is short for identification, spelled with 2 capital letters. This matches the intention of OrderID as “Order identification“.

ID wins

WIKIPEDIA

In Wikipedia we can learn that Id is part of the Freudian psyche structural model, and is used to describe the Id, ego and super-ego. When searching for id however, the 2 capital letters “ID” leads to an article on Identity Document and Identifier, the intended description of the ID in OrderID.

ID wins

MICROSOFT .NET CAPITALIZATION CONVENTIONS

Microsoft does not agree with Merriam-Webster, and clearly states that Id should be written with one capital letter, as OrderId.

Id wins

COLLINS DICTIONARY

The Collins dictionary also states that ID is a noun and means identity or identification, while Id is short for Idaho.

ID wins

CAMEL CASING

You would think that camel casing would vote for Id, but no, when you have two letters, both should be upper case. When you have three, only the first one should be upper case.

ID wins

GOOGLE DEVELOPER DOCUMENTATION STYLE GUIDE

Google is on the ID team, unless its in string literals and enums:

Not Id or id, except in string literals or enums. In some contexts, best to spell out as identifier or identification.

ID wins, but since Id is allowed in some contexts, I will say this is a draw.

CONCLUSION

Well, ID is the winner over Id with a 5-2 victory. This does not make you a bad programmer should you choose to use Id instead of ID. As long as you do it consistently. And if in doubt, you could always waste a few more bytes and spell out the entire ID:

  • OrderIdentity

But tell me what do you think? OrderID or OrderId?

OrderID or OrderId

To orderID or orderId, that’s the question 

MORE TO READ:

Posted in General .NET | Tagged , , | 1 Comment

C# HttpClient POST or PUT Json with content type application/json

The HttpClient is a nifty tool for getting and sending data to a URL, but it works differently from the old fashioned WebRequest class.

The content type is added to the post data instead of added as a header parameter. So if you with to PUT or POST Json data, and you need to set the content type to application/json, specify the content type in the parameters of the StringContent that you POST:

string json;

var content = new StringContent(
  json, 
  System.Text.Encoding.UTF8, 
  "application/json"
  );

An example of a complete POST method that can take an object and POST it as Json could look like this:

using System.Net.Http;
using Newtonsoft.Json;

private static HttpClient _httpClient = new HttpClient();

public bool POSTData(object json, string url)
{
  using (var content = new StringContent(JsonConvert.SerializeObject(json), System.Text.Encoding.UTF8, "application/json"))
  {
    HttpResponseMessage result = _httpClient.PostAsync(url, content).Result;
    if (result.StatusCode == System.Net.HttpStatusCode.Created)
      return true;
    string returnValue = result.Content.ReadAsStringAsync().Result;
    throw new Exception($"Failed to POST data: ({result.StatusCode}): {returnValue}");
  }
}

This method is not utilizing the async properties of the HttpClient class. Click here to see how to use HttpClient with async.

MORE TO READ:

Posted in .net, .NET Core, c#, General .NET | Tagged , , , | Leave a comment

C# Newtonsoft serialize property that is an array and a string

IdentityServer4 has a strange way of returning roles. If there is only one role, the property is a string:

{
    "sub": "55568182-9273-4c6f-8d61-6555f7f02551",
    "role": "role001"
}

But if there is more than one role, the property is an array of strings:

{
    "sub": "55568182-9273-4c6f-8d61-6555f7f02551",
    "role": [
        "role001",
        "role002"
    ]
}

If the property is a string, and you try to serialize this into an IEnumerable<string>, you get the following error:

Could not cast or convert from System.String to System.Collections.Generic.IEnumerable`1[System.String].

JsonSerializationException: Error converting value “xxx” to type ‘System.Collections.Generic.IEnumerable`1[System.String]

Or

Could not cast or convert from System.String to System.String[].

JsonSerializationException: Error converting value “xxx” to type ‘System.String[]’

To serialize this into a model class, you will need to implement a JsonConverter, which is a custom converter that, in this case, checks if the property is one object or several before serializing.

STEP 1: IMPLEMENT THE CUSTOM CONVERTER

This JsonConverter checks the property before serializing to see if the property is an array or not. If not, it will output the single item as an array:

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;

namespace MyCode
{
  internal class CustomArrayConverter<T> : JsonConverter
  {
    public override bool CanConvert(Type objectType)
    {
      return (objectType == typeof(List<T>));
    }

    public override object ReadJson(
      JsonReader reader,
      Type objectType,
      object existingValue,
      JsonSerializer serializer)
    {
      JToken token = JToken.Load(reader);
      if (token.Type == JTokenType.Array)
        return token.ToObject<List<T>>();
      return new List<T> { token.ToObjec<T>() };
    }

    public override bool CanWrite
    {
      get
      {
        return false;
      }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
      throw new NotImplementedException();
    }
  }
}

STEP 2: USE THE CUSTOM CONVERTER IN THE MODEL CLASS:

The custom converter is used as a property on the attribute:

using System;
using System.Collections.Generic;
using Newtonsoft.Json;

namespace MyCode
{
  [Serializable]
  public class UserInfoModel
  {
    [JsonProperty("sub")]
    public string ID { get; set; }

    [JsonProperty("role")]
    [JsonConverter(typeof(CustomArrayConverter<string>))]
    public IEnumerable<string> Roles { get; set; }
  }
}

The CustomArrayConverter will work on any type.

MORE TO READ: 

Posted in .net, .NET Core, c#, General .NET | Tagged , , | Leave a comment

Which of my old Sitecore posts are still valid in Sitecore 9?

I have been writing Sitecore blog posts since April 2006. The first ones were for Sitecore 4.2. Now, 13 years later, some of the old posts are still valid, while others are obsolete as the Sitecore API have changed.

But which ones will still work on a Sitecore 9 installation?

I have skipped articles on XSLT and very specific WebForms articles. Although XSLT and WebForms are still around and working, no new projects should be based on these technologies.

First the shortlist. These articles are top 10 reads:

  1. LinkManager – Working with URL’s in Sitecore 6
  2. Sitecore.Links.LinkManager and the context
  3. Sitecore: Setting up Security on Languages
  4. Multiple languages in Sitecore
  5. Run Sitecore scheduled task at the same time every day
  6. Sitecore Image Parameters
  7. Create and publish items in Sitecore
  8. Sitecore Links with LinkManager and MediaManager
  9. Sitecore Virtual Users – authenticate users from external systems
  10. Sitecore Scheduled Tasks – Run on certain server instance

This is the list of all articles not specifically written for Sitecore 9 but the code still works. There are almost 60 articles not Sitecore 9 based, but still relevant:

2006

2008

2009

2010

2011

2012

2013

2014

2015

2016

2017

From 2017, Sitecore 9 was the standard, and I started writing articles on Sitecore 9. All articles from 2017 and forward will help you in Sitecore 9 development.

Posted in .net, .NET Core, c#, General .NET, Sitecore, Sitecore 4, Sitecore 5, Sitecore 6, Sitecore 7, Sitecore 8, Sitecore 9 | Tagged | Leave a comment

Sitecore CTRL+S fires item:saving event twice

If you have ever tried to implement your own Sitecore item:saving or item:saved event handlers, or have tried to extend the SaveUI pipeline, you might have noticed that the events gets fired twice if you click [CTRL]+[S], but only once if you click the [Save] button.

If this happens to you, check the Save button chunk in the CORE database.

  • Go to /sitecore/content/Applications/Content Editor/Ribbons/Chunks/Write/Save
    itemid: {12FF26DE-2BBC-4E60-A43A-735DF841E3CA}
  • Find the “Key Code” field.
  • Clear the field
/sitecore/content/Applications/Content Editor/Ribbons/Chunks/Write/Save

/sitecore/content/Applications/Content Editor/Ribbons/Chunks/Write/Save

It seems that the key code c83 will activate the SaveUI pipeline on [CTRL]+[S] even when Sitecore already called it.

MORE TO READ:

 

Posted in Sitecore 5, Sitecore 6, Sitecore 7, Sitecore 8, Sitecore 9 | Tagged , , , | 2 Comments

IdentityServer use IdentityModel to get user token and user info

Using IdentityServer have been made easier with the IdentityModel helper library. The library implements extension methods that maps the input and output to classes.

GET ACCESS TOKEN:

Use the PasswordTokenRequest and RequestPasswordTokenAsync to get the access token. replace your_domain, your_clientid and your_secret with the values from your Identity Server:

using IdentityModel.Client;

public string LoginUsingIdentityServer(string username, string password)
{
  var client = new HttpClient();
  PasswordTokenRequest tokenRequest = new PasswordTokenRequest() 
  { 
    Address = "http://your_domain/connect/token",
    ClientId = "your_clientid",
    ClientSecret = "your_secret",
    UserName = username,
    Password = password
  };
  
  var response = client.RequestPasswordTokenAsync(tokenRequest).Result;
  if (!response.IsError)
  {
     response.AccessToken;
  }
  else
  {
    throw new Exception("Invalid username or password");
  }
}

GET USERINFO:

First you need to ensure that the Identity Resource in Identity Server can return the claims from the user (claims is the fancy word for properties). If you use the Identity Server Admin interface, you can choose OPENID from the Resources/Identity Resources menu and select the claims visually:

Identity Server Claims

Identity Server Claims

Use the UserInfoRequest and pass your access token to GetUserInfoAsync to get the user claims:

using IdentityModel.Client;
using Newtonsoft.Json;
using System.Collections;
using System.Net.Http;

public void GetUserInfo(string accessToken)
{
  var client = new HttpClient();
  var userInfoRequest = new UserInfoRequest()
  {
    Address = "http://your_server/connect/userinfo",
    Token = accessToken
  };

  var response = client.GetUserInfoAsync(userInfoRequest).Result;
  if (response.IsError)
    throw new Exception("Invalid accessToken");

  dynamic responseObject = JsonConvert.DeserializeObject(response.Raw);
  if (responseObject.given_name != null && responseObject.family_name != null)
  {
    // do something with the name
    string name = responseObject.given_name.ToString() 
	              + " " 
				  + responseObject.family_name.ToString();
    }

    // If there is one role, the role property is a string.
	// There there is more roles, the role property is an array
	// To handle this, I use this clumsy statement:
    if (responseObject.role.Type == Newtonsoft.Json.Linq.JTokenType.String)
	{
      // do something with the roles
	string role = responseObject.role.ToString();
	}	
    else
    {
      foreach (var role in responseObject.role)
      {
        // do something with the roles
        string role = role.ToString();
      }
    }
  }
}

FOR SITECORE USERS:

If you use Sitecore, you can create a Virtual User based on the user info, and log into Sitecore using this virtual user.

using IdentityModel.Client;
using Newtonsoft.Json;
using Sitecore.Security.Authentication;
using System.Collections;
using System.Net.Http;

namespace MyCode
{
  public class VirtualUserFactory
  {
    public static Sitecore.Security.Accounts.User Create(string identityServerAccessToken)
    {
      var client = new HttpClient();
      var userInfoRequest = new UserInfoRequest()
      {
        Address = "http://your_server/connect/userinfo",
        Token = identityServerAccessToken
      };

      var response = client.GetUserInfoAsync(userInfoRequest).Result;
      if (response.IsError)
        throw new Sitecore.Exceptions.AccessDeniedException();

      dynamic responseObject = JsonConvert.DeserializeObject(response.Raw);

      Sitecore.Security.Accounts.User virtualUser = AuthenticationManager.BuildVirtualUser("extranet\\" + responseObject.preferred_username.ToString(), true);

      if (responseObject.given_name != null && responseObject.family_name != null)
        virtualUser.Profile.FullName = responseObject.given_name.ToString() + " " + responseObject.family_name.ToString();

      if (responseObject.email != null)
        virtualUser.Profile.Email = responseObject.email.ToString();

      if (responseObject.role.Type == Newtonsoft.Json.Linq.JTokenType.String)
        virtualUser.Roles.Add(Sitecore.Security.Accounts.Role.FromName(responseObject.role.ToString()));
      else
      {
        foreach (var role in responseObject.role)
        {
          virtualUser.Roles.Add(Sitecore.Security.Accounts.Role.FromName(role.ToString()));
        }
      }

      if (responseObject.sub != null)
        virtualUser.Profile.ProfileItemId = responseObject.sub.ToString();

      return virtualUser;
    }
  }
}

Logging into Sitecore uisng a virtual user is simple:

// Login the virtual user
Sitecore.Security.Authentication.AuthenticationManager.LoginVirtualUser(virtualUser);

MORE TO READ:

Posted in .net, .NET Core, c#, General .NET, Sitecore 6, Sitecore 7, Sitecore 8, Sitecore 9 | Tagged , , , , | Leave a comment

Sitecore use Application Insights Metrics and Telemetry

Although Sitecore have an integration for Application Insights (which is part of Azure Monitor), you can implement the TelemetryClient yourself. This is useful if you wish to log Metrics or Trace your own code, without involving the complete Sitecore solution. There is no need for configuration changes if all you want is to get some metrics for your own subsystem within Sitecore.

Application Insights is very easy to work with. First you need to install the Microsoft.ApplicationInsights NuGet Package.

In order to use Application Insights you must create a TelemetryClient:

using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.Extensibility;

namespace MyCode
{
  public static class TelemetryFactory
  {
    /// <summary>
    /// Create a new TelemetryClient
    /// </summary>
    /// <param name="instrumentationKey">The Application Insights key</param>
    /// <param name="roleInstance">The name of your instance</param>
    /// <param name="roleName">The name of your role</param>
    /// <returns></returns>
    public static TelemetryClient GetTelemetryClient(string instrumentationKey, string roleInstance, string roleName)
    {
      var telemetryConfiguration = new TelemetryConfiguration {InstrumentationKey = instrumentationKey};
      telemetryConfiguration.TelemetryInitializers.Add(new OperationCorrelationTelemetryInitializer());
      telemetryConfiguration.TelemetryInitializers.Add(new CloudRoleInitializer(roleInstance, roleName));
      return new TelemetryClient(telemetryConfiguration);
    }
  }
}

With the TelemetryClient in hand, it’s easy to track metrics:

TelemetryClient _telemetryClient = 
  TelemetryFactory.GetTelemetryClient("my-key", "Application Name", "Application Name");

_telemetryClient.GetMetric(
  new MetricIdentifier("MySystem", $"Deleted Users")
).TrackValue(1);

The tracked telemetry will appear in Application Insights shortly after:

Application Insights Metrics

Application Insights Metrics

If you wish to log traces, it is equally easy:

var trace = new TraceTelemetry
{
  SeverityLevel = SeverityLevel.Information,
  Message = $"xxx Deleted User xxx"
};

_telemetryClient.TrackTrace(trace);

The traces appear as log lines in Application Insights:

Application Insights Trace

Application Insights Trace

MORE TO READ:

Posted in .net, c#, Microsoft Azure, Sitecore 7, Sitecore 8, Sitecore 9 | Tagged , , | Leave a comment