Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (seongtaejeong at gmail.com)
홈페이지
첨부 파일
 

(시리즈 글이 2개 있습니다.)
.NET Framework: 786. ASP.NET - HttpCookieCollection을 다중 스레드에서 사용할 경우 무한 루프 현상
; https://www.sysnet.pe.kr/2/0/11587

디버깅 기술: 221. WinDbg 분석 사례 - ASP.NET HttpCookieCollection을 다중 스레드에서 사용할 경우 무한 루프 현상 - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/13916




WinDbg 분석 사례 - ASP.NET HttpCookieCollection을 다중 스레드에서 사용할 경우 무한 루프 현상 - 두 번째 이야기

아래와 같이 덤프 파일을 분석해,

windbg 분석 사례 - x86 ASP.NET 웹 응용 프로그램의 CPU 100% 현상 (4)
; https://www.sysnet.pe.kr/2/0/13914

requestValidationMode 값을 4.0으로 설정하면 문제가 발생하지 않을 거라고 했는데, 그래도 여전히 해당 현상이 재현된다는 소식과 함께 다시 덤프 파일을 받았습니다.

이럴 땐 자연스럽게, ^^; 제 눈으로 본 것이 아니기 때문에 정말 4.0으로 설정했는지 확인해야 합니다. 어떻게 할 수 있을까요? ^^

우선 HttpRuntimeSection.RequestValidationMode 속성의 역어셈블 코드를 보면,

[ConfigurationProperty("requestValidationMode", DefaultValue = "4.0")]
[TypeConverter(typeof(VersionConverter))]
public Version RequestValidationMode
{
    get
    {
        if (this._requestValidationMode == null)
        {
            this._requestValidationMode = (Version)base[HttpRuntimeSection._propRequestValidationMode];
        }
        return this._requestValidationMode;
    }
    set
    {
        this._requestValidationMode = value;
        base[HttpRuntimeSection._propRequestValidationMode] = value;
    }
}

보는 바와 같이 _requestValidationMode 필드에 값이 보관됩니다. 다행히 HttpRuntimeSection은 Web Application에 1개의 인스턴스, 또는 다른 이유로 인해 1~2개 정도 더 생성될 것이라고 짐작할 수 있으므로 GC Heap에 존재할 그 인스턴스를 검색해 보면 됩니다.

// 우선 해당 타입의 MethodTable을 알아내고,
0:000> !name2ee System.Web.dll!System.Web.Configuration.HttpRuntimeSection
Module:      19c1a4c4
Assembly:    System.Web.dll
Token:       020006fe
MethodTable: 1a7c4248
EEClass:     1b5966e0
Name:        System.Web.Configuration.HttpRuntimeSection

// GC Heap에서 HttpRuntimeSection 인스턴스를 검색
0:000> !dumpheap -mt 1a7c4248
 Address       MT     Size
0b6522c8 1a7c4248      112     
0b652438 1a7c4248      112     

Statistics:
      MT    Count    TotalSize Class Name
1a7c4248        2          224 System.Web.Configuration.HttpRuntimeSection
Total 2 objects
Fragmented blocks larger than 0.5 MB:
    Addr     Size      Followed by
0a862394    0.6MB         0a8f6234 System.Byte[]

보는 바와 같이 2개의 인스턴스가 있는데요, 각각을 덤프해 보면,

// 인스턴스 1개는 _requestValidationMode 필드가 비어 있고,

0:000> !do 0b6522c8
Name:        System.Web.Configuration.HttpRuntimeSection
MethodTable: 1a7c4248
EEClass:     1b5966e0
Size:        112(0x70) bytes
File:        C:\Windows\Microsoft.Net\assembly\GAC_32\System.Web\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Web.dll
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
0663aa48  4000198       30       System.Boolean  1 instance        0 _bDataToWrite
...[생략]...
0663c9b0  4002e15       50         System.Int32  1 instance       -1 _RequestLengthDiskThresholdBytes
1a62e23c  4002e17       3c       System.Version  0 instance 00000000 _requestValidationMode
0663c9b0  4002e18       54         System.Int32  1 instance        0 _MaxUrlLength
...[생략]...

// 다른 인스턴스에는 4.0.-1.-1 값이 설정됨

0:000> !do 0b652438
Name:        System.Web.Configuration.HttpRuntimeSection
MethodTable: 1a7c4248
EEClass:     1b5966e0
Size:        112(0x70) bytes
File:        C:\Windows\Microsoft.Net\assembly\GAC_32\System.Web\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Web.dll
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
0663aa48  4000198       30       System.Boolean  1 instance        0 _bDataToWrite
...[생략]...
0663c9b0  4002e15       50         System.Int32  1 instance    81920 _RequestLengthDiskThresholdBytes
1a62e23c  4002e17       3c       System.Version  0 instance 0b652664 _requestValidationMode
0663c9b0  4002e18       54         System.Int32  1 instance      260 _MaxUrlLength
...[생략]...

0:000> !do 0b652664
Name:        System.Version
MethodTable: 1a62e23c
EEClass:     1a63b0c4
Size:        24(0x18) bytes
File:        C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
0663c9b0  400073a        4         System.Int32  1 instance        4 _Major
0663c9b0  400073b        8         System.Int32  1 instance        0 _Minor
0663c9b0  400073c        c         System.Int32  1 instance       -1 _Build
0663c9b0  400073d       10         System.Int32  1 instance       -1 _Revision
19c14e2c  400073e      1d0        System.Char[]  0   shared   static SeparatorsArray

그러니까, requestValidationMode 값을 4.0으로 설정한 것이 맞고, 그럼에도 불구하고 문제가 발생하고 있는 것입니다.




그렇다면, 분명히 어떤 식으로든 EnableGranularValidation 코드가 수행되고 있다는 건데요, 이건 어떻게 확인할 수 있을까요? ^^

이를 위해 WinDbg를 동원할 필요는 없고, 위의 분석 대상이었던 응용 프로그램의 유형이 ASP.NET MVC 프로젝트였으므로 이에 대한 간단한 예제 프로젝트를 Visual Studio에서 생성한 후 쿠키를 사용하는 코드를 넣어,

public class HomeController : Controller
{
    public ActionResult Index()
    {
        var oldCookie = this.Request.Cookies["test"];
        this.Response.Cookies.Add(new HttpCookie("test", "testValue") { Expires = DateTime.Now.AddDays(1) });

        return View();
    }

    // ...[생략]...
}

디버깅을 시작해 솔루션 탐색기의 "External Sources"로부터 HttpCookieCollection 소스 코드를 열어 BP를 걸어보면 됩니다.

cookie_validation_on_mvc_1.png

실제로, web.config의 requestValidationMode를 4.0으로 설정해도,

<httpRuntime targetFramework="4.8" requestValidationMode="4.0" />

동일하게 BP가 걸리는데요, 이때의 callstack을 따라가 보면,

>    System.Web.dll!System.Web.HttpCookieCollection.EnableGranularValidation(System.Web.ValidateStringCallback validationCallback = {Method = Inspecting the state of an object in the debuggee of type System.Delegate is not supported in this context.}) Line 127 C#  Symbols loaded.
    System.Web.dll!System.Web.HttpRequest.Cookies.get() Line 1225   C#  Symbols loaded.
    net48_mvc.dll!net48_mvc.Controllers.HomeController.Index() Line 14  C#  Symbols loaded.
    [Lightweight Function]      Annotated Frame
    System.Web.Mvc.dll!System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(System.Web.Mvc.ControllerContext controllerContext = {System.Web.Mvc.ControllerContext}, System.Web.Mvc.ActionDescriptor actionDescriptor = {System.Web.Mvc.ReflectedActionDescriptor}, System.Collections.Generic.IDictionary<string, object> parameters) Line 253  C#  Symbols loaded.
    System.Web.Mvc.dll!System.Web.Mvc.Async.AsyncControllerActionInvoker.BeginInvokeSynchronousActionMethod.AnonymousMethod__9_0(System.IAsyncResult asyncResult, System.Web.Mvc.Async.AsyncControllerActionInvoker.ActionInvocation innerInvokeState) Line 304 C#  Symbols loaded.
    System.Web.Mvc.dll!System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult<System.__Canon, System.Web.Mvc.Async.AsyncControllerActionInvoker.ActionInvocation>.CallEndDelegate(System.IAsyncResult asyncResult) Line 224   C#  Symbols loaded.
    // ...[생략]...
    System.Web.dll!System.Web.Hosting.PipelineRuntime.ProcessRequestNotification(System.IntPtr rootedObjectsPointer, System.IntPtr nativeRequestContext, System.IntPtr moduleData, int flags) Line 386  C#  Symbols loaded.
    [AppDomain Transition]      Annotated Frame

관련 코드의 역어셈블 결과에서,

// HttpRequest.cs

public HttpCookieCollection Cookies
{
    get
    {
        EnsureCookies();
        if (_flags[4])
        {
            _flags.Clear(4);
            ValidateCookieCollection(_cookies);
        }
        return _cookies;
    }
}

private void ValidateCookieCollection(HttpCookieCollection cc)
{
	if (GranularValidationEnabled)
	{
		cc.EnableGranularValidation(delegate(string key, string value)
        {
            ValidateString(value, key, RequestValidationSource.Cookies);
        });
		return;
	}
	int count = cc.Count;
	for (int i = 0; i < count; i++)
	{
		string key2 = cc.GetKey(i);
		string value2 = cc.Get(i).Value;
		if (!string.IsNullOrEmpty(value2))
		{
			ValidateString(value2, key2, RequestValidationSource.Cookies);
		}
	}
}

Cookies get 메서드로부터 결국 _flags[4] 값으로 인해 EnableGranularValidation이 호출되고 있음을 알 수 있습니다. 그리고 다시, 그것은 GranularValidationEnabled get 속성이 true를 반환하기 때문인데,

// HttpRequest.cs

private const int granularValidationEnabled = 1073741824; // 0x40000000

private bool GranularValidationEnabled => _flags[0x40000000]; // 0x40000000

internal void EnableGranularRequestValidation()
{
	_flags[0x40000000] = true;
}

따라서 EnableGranularRequestValidation 메서드가 호출된 적이 있다는 것이므로 이것도 BP를 걸어 확인하면, 호출 스택이 이렇게 나오고,

>    System.Web.dll!System.Web.HttpRequest.EnableGranularRequestValidation() Line 2672   C#  Symbols loaded.
    System.Web.Mvc.dll!System.Web.Mvc.MvcHandler.ProcessRequestInit(System.Web.HttpContextBase httpContext = {System.Web.HttpContextWrapper}, out System.Web.Mvc.IController controller = null, out System.Web.Mvc.IControllerFactory factory = null) Line 168  C#  Symbols loaded.
    System.Web.Mvc.dll!System.Web.Mvc.MvcHandler.BeginProcessRequest(System.Web.HttpContextBase httpContext, System.AsyncCallback callback = {Method = {System.Reflection.RuntimeMethodInfo}}, object state = null) Line 86 C#  Symbols loaded.
    System.Web.dll!System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() Line 648 C#  Symbols loaded.
    System.Web.dll!System.Web.HttpApplication.ExecuteStepImpl(System.Web.HttpApplication.IExecutionStep step) Line 3596 C#  Symbols loaded.
    System.Web.dll!System.Web.HttpApplication.ExecuteStep(System.Web.HttpApplication.IExecutionStep step = {System.Web.HttpApplication.CallHandlerExecutionStep}, ref bool completedSynchronously = false) Line 3622    C#  Symbols loaded.
    System.Web.dll!System.Web.HttpApplication.PipelineStepManager.ResumeSteps(System.Exception error) Line 1161 C#  Symbols loaded.
    System.Web.dll!System.Web.HttpApplication.BeginProcessRequestNotification(System.Web.HttpContext context, System.AsyncCallback cb) Line 4054    C#  Symbols loaded.
    System.Web.dll!System.Web.HttpRuntime.ProcessRequestNotificationPrivate(System.Web.Hosting.IIS7WorkerRequest wr = {System.Web.Hosting.IIS7WorkerRequest}, System.Web.HttpContext context = {System.Web.HttpContext}) Line 1407  C#  Symbols loaded.
    System.Web.dll!System.Web.Hosting.PipelineRuntime.ProcessRequestNotificationHelper(System.IntPtr rootedObjectsPointer, System.IntPtr nativeRequestContext = 0x0000026e78687998, System.IntPtr moduleData, int flags) Line 461   C#  Symbols loaded.
    System.Web.dll!System.Web.Hosting.PipelineRuntime.ProcessRequestNotification(System.IntPtr rootedObjectsPointer, System.IntPtr nativeRequestContext, System.IntPtr moduleData, int flags) Line 386  C#  Symbols loaded.
    [Native to Managed Transition]      Annotated Frame
    [Managed to Native Transition]      Annotated Frame
    System.Web.dll!System.Web.Hosting.PipelineRuntime.ProcessRequestNotificationHelper(System.IntPtr rootedObjectsPointer, System.IntPtr nativeRequestContext, System.IntPtr moduleData, int flags) Line 499    C#  Symbols loaded.
    System.Web.dll!System.Web.Hosting.PipelineRuntime.ProcessRequestNotification(System.IntPtr rootedObjectsPointer, System.IntPtr nativeRequestContext, System.IntPtr moduleData, int flags) Line 386  C#  Symbols loaded.
    [AppDomain Transition]      Annotated Frame

Mvc 프레임워크에 놓인 ProcessRequestInit 메서드에서 ValidationUtility.EnableDynamicValidation 메서드를 호출하며, 그 원인은 다시 ValidationUtility.IsValidationEnabled가 true를 반환하기 때문입니다.


// MvcHandler.cs (System.Web.Mvc.dll)
private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory)
{
    HttpContext current = HttpContext.Current;
    if (current != null && ValidationUtility.IsValidationEnabled(current) == true)
    {
        ValidationUtility.EnableDynamicValidation(current);
    }
    AddVersionHeader(httpContext);
    RemoveOptionalRoutingParameters();
    string requiredString = RequestContext.RouteData.GetRequiredString("controller");
    factory = ControllerBuilder.GetControllerFactory();
    controller = factory.CreateController(RequestContext, requiredString);
    if (controller == null)
    {
        throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, MvcResources.ControllerBuilder_FactoryReturnedNull, new object[2]
        {
            factory.GetType(),
            requiredString
        }));
    }
}

// Microsoft.Web.Infrastructure.dll

[EditorBrowsable(EditorBrowsableState.Never)]
public static class ValidationUtility
{
    // ...[생략]...

    [SecuritySafeCritical]
    public static bool? IsValidationEnabled(HttpContext context)
    {
        if (DynamicValidationShimReflectionUtil.Instance != null)
        {
            return DynamicValidationShimReflectionUtil.Instance.IsValidationEnabled(context);
        }
        return CollectionReplacer.IsValidationEnabled(context);
    }
}

// System.Web.dll

internal static class DynamicValidationShim
{
    internal static bool IsValidationEnabled(HttpContext context)
    {
        return context.Request.ValidateInputWasCalled;
    }

    // ...[생략]...
}

// HttpRequest.cs (System.Web.dll)

internal bool ValidateInputWasCalled => _flags[32768]; // 0x8000

public void ValidateInput()
{
    if (!ValidateInputWasCalled && !RequestValidationSuppressed)
    {
        _flags.Set(32768);
        _flags.Set(1);
        _flags.Set(2);
        _flags.Set(4);
        _flags.Set(64);
        _flags.Set(128);
        _flags.Set(256);
        _flags.Set(512);
        _flags.Set(8);
    }
}

끝이 보이는군요. ^^ 이제 ValidateInput이 호출된 위치를 다시 BP를 걸어 확인해 보면,

internal void ValidateInputIfRequiredByConfig()
{
    RuntimeConfig config = RuntimeConfig.GetConfig(Context);
    HttpRuntimeSection httpRuntime = config.HttpRuntime;
    if (CanValidateRequest())
    {
        string path = Path;
        if (path.Length > httpRuntime.MaxUrlLength)
        {
            throw new HttpException(400, SR.GetString("Url_too_long"));
        }
        if (QueryStringText.Length > httpRuntime.MaxQueryStringLength)
        {
            throw new HttpException(400, SR.GetString("QueryString_too_long"));
        }
        char[] requestPathInvalidCharactersArray = httpRuntime.RequestPathInvalidCharactersArray;
        if (requestPathInvalidCharactersArray != null && requestPathInvalidCharactersArray.Length != 0)
        {
            int num = path.IndexOfAny(requestPathInvalidCharactersArray);
            if (num >= 0)
            {
                string text = new string(path[num], 1);
                throw new HttpException(400, SR.GetString("Dangerous_input_detected", "Request.Path", text));
            }
            _flags.Set(65536);
        }
    }
    Version requestValidationMode = httpRuntime.RequestValidationMode;
    if (requestValidationMode == VersionUtil.Framework00)
    {
        _flags[int.MinValue] = true;
    }
    else if (requestValidationMode >= VersionUtil.Framework40)
    {
        ValidateInput();
        if (requestValidationMode >= VersionUtil.Framework45)
        {
            EnableGranularRequestValidation();
        }
    }
}

이렇게 requestValidationMode >= VersionUtil.Framework40인 상황에서 호출된 것이 MVC Framework에서는 (마이크로소프트가 의도한 것인지, 논리적으로 버그인 것인지 알 수 없으나) 기본적으로 Cookie의 EnableGranularValidation 기능을 활성화시켜버리는 것입니다.

결국, MVC 프로젝트라면 web.config에 requestValidationMode 값을 오히려 4.0 미만으로 설정해야 하는데, 가능한 값이 정해져 있으므로 2.0 정도로 내려야 합니다.




부가적으로 하나 더 정리하자면, ValidationUtility의 경우 중간에 DynamicValidationShimReflectionUtil.Instance를 접근하는데,

// ValidationUtility.cs (Microsoft.Web.Infrastructure.dll)

[SecuritySafeCritical]
public static bool? IsValidationEnabled(HttpContext context)
{
    if (DynamicValidationShimReflectionUtil.Instance != null)
    {
        return DynamicValidationShimReflectionUtil.Instance.IsValidationEnabled(context);
    }
    return CollectionReplacer.IsValidationEnabled(context);
}

이것은 Reflection을 이용해 System.Web.dll에 있는 Microsoft.Web.Infrastructure.DynamicValidationHelper.DynamicValidationShim 타입 정보를 로드하는 역할을 합니다.

// DynamicValidationShimReflectionUtil.cs (Microsoft.Web.Infrastructure.dll)

namespace Microsoft.Web.Infrastructure.DynamicValidationHelper;

[SecurityCritical]
internal sealed class DynamicValidationShimReflectionUtil
{
    // ...[생략]...

    [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We catch and ignore all errors because we do not want to bring down the application.")]
    private static DynamicValidationShimReflectionUtil GetInstance()
    {
        try
        {
            DynamicValidationShimReflectionUtil dynamicValidationShimReflectionUtil = new DynamicValidationShimReflectionUtil();
            Type type = CommonAssemblies.SystemWeb.GetType("Microsoft.Web.Infrastructure.DynamicValidationHelper.DynamicValidationShim", throwOnError: false);
            if (type == null)
            {
                return null;
            }
            MethodInfo method = CommonReflectionUtil.FindMethod(type, "EnableDynamicValidation", isStatic: true, new Type[1] { typeof(HttpContext) }, typeof(void));
            dynamicValidationShimReflectionUtil.EnableDynamicValidation = CommonReflectionUtil.MakeDelegate<Action<HttpContext>>(method);
            CommonReflectionUtil.Assert(dynamicValidationShimReflectionUtil.EnableDynamicValidation != null);
            MethodInfo method2 = CommonReflectionUtil.FindMethod(type, "IsValidationEnabled", isStatic: true, new Type[1] { typeof(HttpContext) }, typeof(bool));
            dynamicValidationShimReflectionUtil.IsValidationEnabled = CommonReflectionUtil.MakeDelegate<Func<HttpContext, bool>>(method2);
            CommonReflectionUtil.Assert(dynamicValidationShimReflectionUtil.IsValidationEnabled != null);
            MethodInfo method3 = CommonReflectionUtil.FindMethod(type, "GetUnvalidatedCollections", isStatic: true, new Type[3]
            {
                typeof(HttpContext),
                typeof(Func<NameValueCollection>).MakeByRefType(),
                typeof(Func<NameValueCollection>).MakeByRefType()
            }, typeof(void));
            dynamicValidationShimReflectionUtil.GetUnvalidatedCollections = CommonReflectionUtil.MakeDelegate<GetUnvalidatedCollectionsCallback>(method3);
            CommonReflectionUtil.Assert(dynamicValidationShimReflectionUtil.GetUnvalidatedCollections != null);
            return dynamicValidationShimReflectionUtil;
        }
        catch
        {
            return null;
        }
    }
}

그리고 System.Web.dll에 있는 DynamicValidationShim은 마치 "Microsoft.Web.Infrastructure.dll"에서 접근할 수 있는 통로를 열어두는 것처럼 구현돼 있습니다.

// DynamicValidationShim.cs (System.Web.dll)
using System;
using System.Collections.Specialized;
using System.Web;

namespace Microsoft.Web.Infrastructure.DynamicValidationHelper;

internal static class DynamicValidationShim
{
    internal static void EnableDynamicValidation(HttpContext context)
    {
        context.Request.EnableGranularRequestValidation();
    }

    internal static bool IsValidationEnabled(HttpContext context)
    {
        return context.Request.ValidateInputWasCalled;
    }

    internal static void GetUnvalidatedCollections(HttpContext context, out Func<NameValueCollection> formGetter, out Func<NameValueCollection> queryStringGetter)
    {
        UnvalidatedRequestValues unvalidated = context.Request.Unvalidated;
        formGetter = () => unvalidated.Form;
        queryStringGetter = () => unvalidated.QueryString;
    }
}

사실 Microsoft.Web.Infrastructure.dll은 System.Web.dll을 직접 참조하고 있으므로 저런 식으로 접근할 필요가 없습니다. 그런데도, 이렇게 구현된 것은...? 아마도 닷넷 프레임워크의 BCL에 기본 포함된 System.Web.dll과는 달리 Microsoft.Web.Infrastructure.dll의 경우 .NET Framework 버전에 의존적이지 않게 만들 목적이었지 않았을까 싶습니다. 일례로, .NET 3.5 버전의 System.Web.dll에는 DynamicValidationShim 타입이 없다는 식의 고려를 한 것이 아닐까...라고 추측해 봅니다. ^^




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 4/23/2025]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 16  17  18  [19]  20  21  22  23  24  25  26  27  28  29  30  ...
NoWriterDateCnt.TitleFile(s)
13464정성태11/28/202310356닷넷: 2174. C# - .NET 7부터 UnmanagedCallersOnly 함수 export 기능을 AOT 빌드에 통합파일 다운로드1
13463정성태11/27/20239723오류 유형: 881. Visual Studio - NU1605: Warning As Error: Detected package downgrade
13462정성태11/27/202310313오류 유형: 880. Visual Studio - error CS0246: The type or namespace name '...' could not be found
13461정성태11/26/202310109닷넷: 2173. .NET Core 3/5+ 기반의 COM Server를 registry 등록 없이 사용하는 방법파일 다운로드1
13460정성태11/26/202310152닷넷: 2172. .NET 6+ 기반의 COM Server 내에 Type Library를 내장하는 방법파일 다운로드1
13459정성태11/26/202310584닷넷: 2171. .NET Core 3/5+ 기반의 COM Server를 기존의 regasm처럼 등록하는 방법파일 다운로드1
13458정성태11/26/202310665닷넷: 2170. .NET Core/5+ 기반의 COM Server를 tlb 파일을 생성하는 방법(tlbexp)
13457정성태11/25/202310326VS.NET IDE: 187. Visual Studio - 16.9 버전부터 추가된 "Display inline type hints" 옵션
13456정성태11/25/202311047닷넷: 2169. C# - OpenAI를 사용해 PDF 데이터를 대상으로 OpenAI 챗봇 작성 [1]파일 다운로드1
13455정성태11/25/202310685닷넷: 2168. C# - Azure.AI.OpenAI 패키지로 OpenAI 사용파일 다운로드1
13454정성태11/23/202311043닷넷: 2167. C# - Qdrant Vector DB를 이용한 Embedding 벡터 값 보관/조회 (Azure OpenAI) [1]파일 다운로드1
13453정성태11/23/20239626오류 유형: 879. docker desktop 설치 시 "Invalid JSON string. (Exception from HRESULT: 0x83750007)"
13452정성태11/22/202310167닷넷: 2166. C# - Azure OpenAI API를 이용해 사용자가 제공하는 정보를 대상으로 검색하는 방법파일 다운로드1
13451정성태11/21/202310252닷넷: 2165. C# - Azure OpenAI API를 이용해 ChatGPT처럼 동작하는 콘솔 응용 프로그램 제작파일 다운로드1
13450정성태11/21/202310056닷넷: 2164. C# - Octokit을 이용한 GitHub Issue 검색파일 다운로드1
13449정성태11/21/202310519개발 환경 구성: 688. Azure OpenAI 서비스 신청 방법
13448정성태11/20/202310268닷넷: 2163. .NET 8 - Dynamic PGO를 결합한 성능 향상파일 다운로드1
13447정성태11/16/202310627닷넷: 2162. ASP.NET Core 웹 사이트의 SSL 설정을 코드로 하는 방법
13446정성태11/16/202310544닷넷: 2161. .NET Conf 2023 - Day 1 Blazor 개요 정리
13445정성태11/15/202311560Linux: 62. 리눅스/WSL에서 CA 인증서를 저장하는 방법
13444정성태11/15/202310756닷넷: 2160. C# 12 - Experimental 특성 지원
13443정성태11/14/202310475개발 환경 구성: 687. OpenSSL로 생성한 사용자 인증서를 ASP.NET Core 웹 사이트에 적용하는 방법
13442정성태11/13/202310157개발 환경 구성: 686. 비주얼 스튜디오로 실행한 ASP.NET Core 사이트를 WSL 2 인스턴스에서 https로 접속하는 방법
13441정성태11/12/202310761닷넷: 2159. C# - ASP.NET Core 프로젝트에서 서버 Socket을 직접 생성하는 방법파일 다운로드1
13440정성태11/11/20239623Windows: 253. 소켓 Listen 시 방화벽의 Public/Private 제어 기능이 비활성화된 경우
13439정성태11/10/202311501닷넷: 2158. C# - 소켓 포트를 미리 시스템에 등록/예약해 사용하는 방법(Port Exclusion Ranges)파일 다운로드1
... 16  17  18  [19]  20  21  22  23  24  25  26  27  28  29  30  ...