Deploy a dotnet core site on nginx and systemd

This article is about how to deploy an ASP.Net core 3.1 site on nginx and systemd.

Preparation:

  • Prepare a server with nginx and systemd.
  • Install dotnet core support on server. Please check Microsoft site for details.
  • Build the binary files of the site to be deployed.

Step 1: Upload files

Make a folder in the server to be used to store site files. This folder will be marked as <SITEPATH> in all files below.

Upload your site files into this folder.

Give the permission to this folder.

sudo chown -R www-data:www-data <SITEPATH>
sudo chmod -R 755 <SITEPATH>

Step 2: Create systemd service

Create a service file. I suggest to put this file in the same folder of the site, aka <SITEPATH>. Let’s name it as myapp. You could change the name.
nano <SITEPATH>/myapp.service and enter this text below:

[Unit]
Description=<A_DESCRIPTION_TEXT_HERE>

[Service]
Environment=ASPNETCORE_URLS=http://localhost:<PORT_NUMBER>
Environment=ASPNETCORE_ENVIRONMENT=Production
WorkingDirectory=<SITEPATH>
ExecStart=/usr/bin/dotnet <SITEPATH>/<ENTRY_FILE>.dll
SyslogIdentifier=<A_NAME_HERE>
Restart=always
RestartSec=10
KillSignal=SIGINT
User=www-data

[Install]
WantedBy=multi-user.target

You should specify the description, port number, site path, entry file (main file), and the name to be used in syslog. Port number need to be different than all used by other services.

Link the file to systemd folder by ln -s <SITEPATH>/myapp.service /etc/systemd/system, reload systemd by systemctl daemon-reload, then start the service by systemctl start myapp.service. If everything goes will, you can see the port is listed in lsof -i -P -n | grep LISTEN. At last, set this service to start with system by systemctl enable myapp.service.

Step 3: Create nginx site.

Create the site file in sites-available folder by nano /etc/nginx/sites-available/<YOUR_SITE_NAME>, and enter this text below:

server {
    listen 80;
    listen [::]:80;
    server_name <SERVER_DOMAIN>;
    
    location / {
        proxy_pass http://localhost:<PORT_NUMBER>;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection keep-alive;
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

You should specify the server domain name and the port number which is chosen for this app.

Link the file to enabled sites by ln -s /etc/nginx/sites-available/<YOUR_SITE_NAME> /etc/nginx/sites-enabled. Test config by nginx -t. If there is nothing wrong, apply the setting by systemctl reload nginx.

Further: Certbot

If you want to use certbot to apply a free ssl certificate to this site, the nginx plugin shipped with certbot can handle that without any problem. Use certbot with the nginx parameter to finish this job: certbot --nginx.

Bug and workaround in dotnet MEF — The export is not assignable to type .

Today, while writing an app use netfx 4.8 with MEF support based on System.ComponentModel.Composition 4.5 from nuget, a strange bug hit me.

Because I’m not treat MEF as essential, I’d like to write the main code in a project and create MEF wrap in another one like:

  • In Project A: class Real : InterfaceA
  • In Project B: class MEFWrap : Real, and marked with Export attribute.

Certainly, Project A is referenced by Project B.  While building the solution, both dlls generated from Project A and Project B are placed in the output folder of Project B.

While trying to use MEF to load the class from Project B, the funny thing happened:

  • If AssemblyCatalog is chosen to create the object, from the assembly object loaded by LoadFrom method, nothing is wrong, but
  • If AggregateCatalog is used with all assemblies loaded from the folder (Project A and Project B) at the same time, an exception raised while calling ComposesParts:

    The export InterfaceA is not assignable to type InterfaceA.

I don’t know why it’s happened but here are 2 ways to get avoid of it.

  • Only load assembly Project B, not both of them, if it’s possible, when you know the name or name pattern while searching files, or
  • Using AssemblyCatalog for each file, instead of using AggregateCatalog as a whole, will also works.

 

 

Recursive Enumerator

using System;
using System.Collections.Generic;

namespace SecretNest.RecursiveEnumerator
{
    /// <summary>
    /// Get the enumerator for querying the parents of specified item.
    /// </summary>
    /// <typeparam name="T">Item type</typeparam>
    /// <param name="current">Item for querying parents</param>
    /// <returns>Enumerator of parents querying</returns>
    public delegate IEnumerator<T> GetParentsEnumerator<T>(T current);

    /// <summary>
    /// Enumerator for querying parents
    /// </summary>
    /// <typeparam name="T">Item type</typeparam>
    public class Enumerator<T> : IEnumerator<T>
    {
        T current, initial;

        Queue<T> notQueried = new Queue<T>();

        HashSet<T> queried = new HashSet<T>(); //for avoiding duplicated query
        Queue<T> rollbackHistory = new Queue<T>(); //for soft reset
        Queue<T> history = new Queue<T>(); //for soft reset
        IEnumerator<T> activeQuery;

        /// <summary>
        /// Callback for getting the enumerator, which is used for querying the parents of specified item.
        /// </summary>
        public GetParentsEnumerator<T> GetParentsEnumeratorCallback { get; set; }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="initial">Initial item</param>
        public Enumerator(T initial)
        {
            notQueried.Enqueue(initial);
            this.initial = initial;
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="initial">Initial item</param>
        /// <param name="callback">Callback for getting the enumerator, which is used for querying the parents of specified item.</param>
        public Enumerator(T initial, GetParentsEnumerator<T> callback)
        {
            notQueried.Enqueue(initial);
            this.initial = initial;
            GetParentsEnumeratorCallback = callback;
        }

        /// <summary>
        /// Gets the current element in the collection.
        /// </summary>
        public T Current
        {
            get { return current; }
        }

        bool disposed;
        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    current = default(T);
                    notQueried = null;
                    queried = null;
                    history = null;
                }
                disposed = true;
            }
        }

        /// <summary>
        /// Gets the current element in the collection.
        /// </summary>
        object System.Collections.IEnumerator.Current
        {
            get { return current; }
        }

        /// <summary>
        /// Skip same items
        /// </summary>
        public bool SkipSameItems { get; set; }

        /// <summary>
        /// Advances the enumerator to the next element of the collection.
        /// </summary>
        /// <returns>true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. </returns>
        public bool MoveNext()
        {
            if (disposed) throw new ObjectDisposedException(null);
            if (rollbackHistory.Count > 0)
            {
                current = rollbackHistory.Dequeue();
                history.Enqueue(current);
                return true;
            }
            if (activeQuery != null)
            {
            here:
                if (activeQuery.MoveNext())
                {
                    if (SkipSameItems && history.Contains(activeQuery.Current)) { goto here; }
                    current = activeQuery.Current;
                    history.Enqueue(current);
                    notQueried.Enqueue(current);
                    return true;
                }
                else
                {
                    activeQuery = null;
                }
            }
            if (GetParentsEnumeratorCallback != null)
            {
                while (notQueried.Count != 0)
                {
                    T item = notQueried.Dequeue();
                    if (!queried.Contains(item))
                    {
                        IEnumerator<T> enumerator = GetParentsEnumeratorCallback(item);
                        queried.Add(item);
                        here:
                        if (enumerator != null)
                        {
                            if (enumerator.MoveNext())
                            {
                                activeQuery = enumerator;
                                if (SkipSameItems && history.Contains(enumerator.Current)) { goto here; }
                                current = enumerator.Current;
                                history.Enqueue(current);
                                notQueried.Enqueue(current);
                                return true;
                            }
                            else
                            {
                                enumerator = null;
                            }
                        }
                    }
                }
            }
            return false;
        }

        /// <summary>
        /// Sets the enumerator to its initial position, which is before the first element in the collection. Keep all histories for caching.
        /// </summary>
        public void Reset()
        {
            if (disposed) throw new ObjectDisposedException(null);
            while (history.Count > 0)
            {
                rollbackHistory.Enqueue(history.Dequeue());
            }
            current = default(T);
        }

        /// <summary>
        /// Sets the enumerator to its initial position, which is before the first element in the collection. Reset all data, and close active sub-query.
        /// </summary>
        public void HardReset()
        {
            if (disposed) throw new ObjectDisposedException(null);
            queried.Clear();
            notQueried.Clear();
            notQueried.Enqueue(initial);
            history.Clear();
            activeQuery = null;
            current = default(T);
        }
    }
}

Download code and demo projects

TreeView with CheckBox

If you wanna use CheckBox enabled TreeView in Windows Vista and further systems, you need to pay attention. If you double click the CheckBoxes in the TreeView, some useful event handlers, like BeforeClick and AfterClick, will not be raised. And, the vision of CheckBoxes will be changed (checked or not) but the property of related TreeNode will not.

To fix this, you have to create your own TreeView instead of the provided one.

using System;
using System.Windows.Forms;

public class FixedTreeView : TreeView
{
	protected override void WndProc(ref Message m)
	{
		// Suppress WM_LBUTTONDBLCLK
		if (m.Msg == 0x203)
		{
			m.Result = IntPtr.Zero;
		}
		else
		{
			base.WndProc(ref m);
		}
	}
}

Source: http://social.msdn.microsoft.com/Forums/en-US/winforms/thread/9d717ce0-ec6b-4758-a357-6bb55591f956

Weak Reference

本文简述dotNet中Weak Reference(弱引用)的功能,并举例在具体应用场景中的效果。

 

首先,让我们回忆一下dotNet CLR的对象生命周期管理。为了解决对象引用计数器以及编码不慎可能导致的内存泄漏,dotNet CLR引入了Garbage Collection(GC)机制。GC会不时的扫描程序,将程序中不再使用的对象进行回收。

那么,怎样的对象才是不再被使用的呢?通常的,我们可以简单认为,在未关联至任何变量(包括对象属性、集合内成员等)的情况下的对象,也就是说,无法被安全的访问到的对象,会被认为是不再被使用的对象。那么,有没有例外呢?

这个例外,就是dotNet中的Weak Reference。它的直接功能,就是封装对一个对象的引用,但不会被GC认做有效的关联。换言之,GC在判断对象是否不再被使用时,不会考虑Weak Reference的引用。

Weak Reference类位于System命名空间下。我们可以通过其构造器直接构造一个对象。System.WeakReference有两个构造器,通常我们使用的第一个,要求传送一个object作为其引用的实际对象(Target属性);第二个构造器需要传送一个bool类型变量,用以定义是否进行长程跟踪,本功能不在本文中进行描述。

当我们构造好一个System.WeakReference后,我们可以通过访问其Target属性,设置或读取其引用的实际变量。由于GC不会考虑Weak Reference中对象的引用,如果Target属性的对象仅被引用于Weak Reference对象时,这个对象则随时可能被GC回收。

我们可以通过判断Weak Reference对象的IsAlive属性,来确定Target属性指向的对象是否已经被回收。当IsAlive为false时,Target属性也将被自动赋值为null。特别的,由于Target属性也可以被显式的赋值为null,我们应该使用IsAlive来进行判断,而不要使用Target属性是否为null来推断。

 

那么,Weak Reference有什么实际用途呢?

有时候,我们需要访问一个对象,却又不想管理其生命周期。例如,我们可能在要某段代码中,将某个窗体对象赋值至一个对象属性中,但我们又不希望由于这个对象未被回收,影响到原窗体的生命周期。如果我们使用Weak Reference对象,代替原属性值,即可避免这种情况的出现。

另外,有些数据,我们并不会经常的访问,但所有的访问又相对密集。这时候我们也可以使用Weak Reference来对数据进行封装。每次访问时,通过判断IsAlive来确定,是否需要构造其Target。由于GC通常不会在系统繁忙时激活,所以这种类似Cache的应用,通常只会在资源不足或系统空闲时才会被回收,而且这种回收是由dotNet CLR负责实现,无需用户编码干预。