Sunday, November 8, 2020

[.NET Core] 400 Bad Request / 'MS-ASPNETCORE-TOKEN' does not match the expected pairing token

At this time, I am testing an old code build by .NET Core 2.0, when I tring to send request to the service, I always got 400 Bad Request, but I can know the service got request because I am running debug mode, it shows request coming and exception happend.


Since I can't find out the error just by 400 bad request, I open "Application Insights Search" in Visual Studio and found this error.

After some digging on stackoverflow, I found the error cause by a old version bug.


This question "Cannot debug in Visual Studio after changing the port number?" 's answer has good explain about this error.

You are getting this error message because Kestrel expected to run behind IIS, and received a direct request. And it noticed that because IIS was not there to inject the MS-ASPNETCORE-TOKEN header. --Gerardo Grignoli

Here have some options to solve the problem:

  1. Call UseUrls() before UseIISIntegration() 
  2. Use Kestrel to debug, not IIS Express 
  3. Upgrade the framework

That's all,

Sunday, November 1, 2020

[.NET core] Localization with Enumeration

In .NET core, doing i18n is more easy than .NET Framework in my opinions, just give appropriate setting, then you can easily assign i18n string to anyplace you want.

Let's see how it work.

1.Add resource file 

Usually I will put my resource file in [Resources] folder, remember put cultureInfo name on resx file like this.









2.Setup Startup.cs

Add following code (depending how many language you support) into your IServiceCollection
//set Support Cultures and default
services.Configure(options =>
{
    var supportedCultures = new[]{
        new CultureInfo("en-US"),
        new CultureInfo("zh-TW"),
        new CultureInfo("ja-JP")
    };
    options.DefaultRequestCulture = new RequestCulture("en-US");
    options.SupportedCultures = supportedCultures;
    options.SupportedUICultures = supportedCultures;
});
//set resource path
services.AddLocalization(options => options.ResourcesPath = "Resources");

Make sure your ResourcesPath set to right path.


3.DI Localizer

Since we are using .NET Core, so we need to DI our Localizer before we using it, for example if you are use it on a controller.
private readonly IStringLocalizer _localizer;
public YourController(IStringLocalizerFactory factory)
{
   _localizer = factory.Create("Message", System.Reflection.Assembly.GetExecutingAssembly().GetName().Name);
}

The "Message" is my resource name, you can DI multi Localizer if you have not only one resource group.


4.Usage

Here is the content of my resource, the usage for Localizer is easy, just use it like a dictionary.
var str = _localizer["Cool"];


If you wanna get a localization string for enum, it was easy too, for example your enum called "WeatherSummary".
var str = _localizer[WeatherSummary.Cool.ToString()];

It was very simple, don't need add custom thing like LocalizedDescriptionAttribute for enum i18n in .NET Framework.


You can check my demo code on github .NET-Core-Localization-and-Enum

Hope you enjoy it.

Sunday, October 25, 2020

[.NET core] System.NotSupportedException: The collection type 'System.Object' on 'somewhere' is not supported.

Somehow if you are using .NET core and return a object on controller, you might meet this exception.
   

  System.NotSupportedException: The collection type 'System.Object' on 'somewhere.InnerResult.Data' is not supported.
   at System.Text.Json.JsonPropertyInfoNotNullable`4.GetDictionaryKeyAndValueFromGenericDictionary(WriteStackFrame& writeStackFrame, String& key, Object& value)
   at System.Text.Json.JsonPropertyInfo.GetDictionaryKeyAndValue(WriteStackFrame& writeStackFrame, String& key, Object& value)
   at System.Text.Json.JsonSerializer.HandleDictionary(JsonClassInfo elementClassInfo, JsonSerializerOptions options, Utf8JsonWriter writer, WriteStack& state)
   at System.Text.Json.JsonSerializer.Write(Utf8JsonWriter writer, Int32 originalWriterDepth, Int32 flushThreshold, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.JsonSerializer.WriteCore(Utf8JsonWriter writer, Object value, Type type, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializer.WriteCore(PooledByteBufferWriter output, Object value, Type type, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializer.WriteCoreString(Object value, Type type, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializer.Serialize[TValue](TValue value, JsonSerializerOptions options)
   at TestWeb.Controllers.AuthController.VerifyToken(String token, Int32 tp) in C:\repos\somewhere\TestWeb\Controllers\AuthController.cs:line 51

In this project, I always return my custom result called InnerResult in API controller.
    public class InnerResult
    {
        public bool Success { get; set; } = true;
        public int Code { get; set; }
        public string Message { get; set; }
        public dynamic Data { get; set; }
    }

If the result json was this format, .NET core default can handle it.
{
    "success": true,
    "code": 0,
    "message": null,
    "data": "vfu3MF5RFlQV9dWNQCFh6iRPNxeezFaV"
}

but if some object in property, it will throw System.NotSupportedException

{
    "success": true,
    "code": 0,
    "message": null,
    "data": {
        "name": "Died"
    }
}

That's because in .NET core , they default using System.Text.Json to deal with json convert, but somehow it can't handle object in property, so......

To solve it, we can use our old friend Json.NET to save the day. (actually, using Microsoft.AspNetCore.Mvc.NewtonsoftJson)

Install-Package Microsoft.AspNetCore.Mvc.NewtonsoftJson -Version 3.1.9

If you are using .NET core 2.x , add .AddNewtonsoftJson() after your AddMVC()

services.AddMvc().AddNewtonsoftJson();

If using .NET core 3.x , add it after what you have.

services.AddControllers().AddNewtonsoftJson();
services.AddControllersWithViews().AddNewtonsoftJson();
services.AddRazorPages().AddNewtonsoftJson();

Then no more this System.NotSupportedExceptio again.



Sunday, October 18, 2020

[.NET core] AddPolicy to Authorize on MVC controller

Sometimes when you are using C# identity, the [Authorize] attribute is not enough,for example, you want additional check not only claims or role, then you can try add policy to make it. 

In this example, we assume a part of method need check token in user claims before access, here is how to implement it. 

1.add a AuthorizationHandler for your policy 
 Handler/AuthorizeTokenHandler.cs
namespace Example.Handler
{
    // Custom AuthorizationHandler for check token in claim
    public class AuthorizeTokenHandler : AuthorizationHandler<TokenRequirement>
    {
        private readonly AuthService _auth;

        public AuthorizeTokenHandler(AuthService auth)
        {
            _auth = auth;
        }
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TokenRequirement requirement)
        {
            //no token claim
            if (!context.User.HasClaim(x => x.Type == "Token"))
            {
                context.Fail();
                return Task.CompletedTask;
            }

            var username = context.User.FindFirst(ClaimTypes.Name).Value;
            var token = context.User.FindFirst("Token").Value;

            #region check token
            if (_auth.CheckToken(username,token))
            {
                context.Succeed(requirement);
            }
            else
            {
                context.Fail();
            }
            #endregion

            return Task.CompletedTask;
        }
    }

    public class TokenRequirement : IAuthorizationRequirement
    {
    }

2. add to startup, in ConfigureServices

            //handler
            services.AddSingleton<IAuthorizationHandler, AuthorizeTokenHandler>();
            
            //add policy
            services.AddAuthorization(options =>
            {
                options.AddPolicy("TokenRequire", policy =>
                    policy.Requirements.Add(new TokenRequirement()));
            });

3. apply policy to Authorize when you need

[Authorize(Policy = "TokenRequire")]
[HttpPost]
public ApiResult SomeMethod()
{
}

That's all, enjoy it.

Sunday, October 11, 2020

[C#] Best Practices of Get Claim from Identity

If you are using ASP.NET Identity, whatever using ASP.NET Core Identy or ASP.NET Identity, usually you will add some claims into the identity and use it later, but I saw lot people using unsafe way to get claim value from identity, it may cause uncatch error if the claim type not exists, let's see the code.
//set some claims first
var claims = new List<Claim>
{
     new Claim(ClaimTypes.Name,"test name"),
     new Claim("Token","token123"),
     new Claim("Number","3")
     //new Claim("dummy","dummy string")
};
var ci = new ClaimsIdentity(claims);

//most seen way, throw error if not exists
var name = ci.Claims.First(x => x.Type == ClaimTypes.Name).Value;
var token = ci.Claims.First(x => x.Type == "Token").Value;  
//using FirstOrDefault() only, throw error if not exists
//var dunno = ci.Claims.FirstOrDefault(x => x.Type == "dunno").Value;

Using this way is easy, but if the claim not exists, if will cause error, whatever you are using First() or FirstOrDefault(). If want to using FirstOrDefault() to get claim value, you can using this fixed way.

            //fixed FirstOrDefault way: return null if not exists
            var dunno = ci.Claims.Where(x => x.Type == "dunno").Select(x => x.Value).FirstOrDefault();
            

but you can use HasClaim method to check claim exist first, and give a proper value if it not exists.

            //safe way: using HasClaim to check
            var dummy = ci.HasClaim(x => x.Type == "dummy")
                ? ci.Claims.First(x => x.Type == "dummy").Value
                : null;
            

or you can choose this short way by using FindFirst method.

            //short way: using FindFirst
            var dummyShort = ci.FindFirst("dummy")?.Value;
            //short way for int
            int.TryParse(ci.FindFirst("Number")?.Value, out var number);
            

I think use ClaimsIdentity.FindFirst Method is the best practices for get claim value.
You can test those here in here

Tuesday, June 2, 2020

幾個關於熊的謠言

最近在網路上發現有人對於熊有些錯誤觀點,這很可能造成真正遇到熊時的人員傷亡,所以找了一下資料,想來澄清一下這些謠言/迷思。

以下資料來源翻譯自Yellowstone Bear World

謠言#1:熊的視力很差

Myth #1: Bears have bad eyesight

這確實是一個常見的錯誤認知。是的,熊確實有驚人的嗅覺,但牠的嗅覺並不是彌補視力的不足,實際上牠們具有很好的視力。

Bears actually have excellent eyesight.
熊在白天的視力與人類相同,但是到了夜晚,牠們出色的視覺才顯出了作用。

就像您家的狗或貓一樣,熊擁有出色的夜視能力。 牠們的眼睛後部有一個稱為脈絡膜層(Tapetum lucidum)的反射膜,該膜可反射光,並使光敏細胞對光進行第二次反應,從而極大地增強了他們在夜間的視力。

這就是為什麼如果您在晚上看到牠們的照片,牠們的眼睛看起來會發綠。

因此,別被騙了...那些熊可能會在您見到牠們之前先見到您! (有關資訊請參見Sylvia Dolson的《熊學 Bear-ology》)

Thursday, December 12, 2019

少女韓國瑜:環保少女 Greta Thunberg


今年Times的年度人物,是來自瑞典的環保少女Greta Thunberg,就先不說她不在投票前三名,得票不到4%是怎麼被操作選上的這種拉基事了,很多人都從媒體片面了解這個人,覺得她很環保很有知識很有行動力啥的,但其實大部分人都被騙了,她就是個少女韓國瑜而已。

Thursday, January 10, 2019

[C#] YOLO3 with OpenCvSharp4



One year ago, I writed [C#] OpenCvSharp DNN with YOLO2, since YOLO3 come out for a while and OpenCV released version 4 on Dec 2018, I think it's time to make a upgrade.

Wednesday, November 21, 2018

[C#] Swashbuckle WebAPI HTTPS BaseUrl and Bearer token

因為Swashbuckle作者跑去專心做Swashbuckle.AspNetCore了,所以在使用上遇到一些問題時要自己想辦法解,這邊紀錄一下遇到的兩個問題。


HTTPS BaseUrl Issue

因為我有用 cloudflare 的 Flexible SSL,在production環境上外面看起來是HTTPS,但Server上走的仍然是HTTP,這時候swagger的 BaseUrl 會因為 Uri Scheme 出問題,所以要來想辦法解,整理需求後,我只需要本機測試(localhost)走HTTP,其他就走HTTPS,按照這個需求來做的解法如下。

//SwaggerConfig.cs
config.EnableSwagger(c =>
{
    c.RootUrl(ResolveBasePath);
    c.Schemes(new[] { "http", "https" });
}

internal static string ResolveBasePath(HttpRequestMessage message)
{
    //fix for Cloudflare Flexible SSL and localhost test
    var scheme = message.RequestUri.Host.IndexOf("localhost") > -1 ? "http://" : "https://";
    return scheme + message.RequestUri.Authority;
}

這樣就可以兼顧正式機與本機開發都正常使用,如果透過proxy那要再另外解,我這邊就先略過。

Wednesday, October 24, 2018

[ASP.NET MVC] Remove response header | 移除回應標頭


有時候做網站會想把一些header移除,但是久久做一次都要查一下,所以決定寫這篇之後就不用查了。



目前使用的版本是MVC 5.2.4,跑在IIS 10上,上面可以看到沒設定前,response有三個header是一般來說大家會去移除的,分別是server, x-aspnet-version, x-powered-by,下面分別說一下移除方法,我是找不用動到IIS設定,改動最小的做法,但是需要改到web.config,如果權限不能碰到web.config的話要另外想辦法。

Server

移除Server header需要在 Global.asax.cs 裡面加上這段
        protected void Application_PreSendRequestHeaders(object sender, EventArgs e)
        {
            var app = sender as HttpApplication;
            // remove Server header
            app?.Context.Response.Headers.Remove("Server");
        }
不過這樣子只能移除由程式產生檔案的header,對於靜態檔像是.js .css之類的還是會有header,所以還有另外一個地方要修改;在web.config內的system.webServer元素內,將 modules 加入runAllManagedModulesForAllRequests="true"這個設定,像是下面這樣,就可以把靜態檔的header也移掉。


X-AspNet-Version

要移除這個header要修改web.config,在httpRuntime element內加入enableVersionHeader="false",像我的情況就是下面這樣。



X-Powered-By

這header也要在web.config內修改,在system.webServer元素內加入customHeaders,像是下面這樣





最後再看看結果,上面三個header都移除了,可賀可喜,收工。