解決 CefSharp WPF控制項不能使用輸入法輸入中文的問題(代碼已提交到 github)

来源:http://www.cnblogs.com/Starts_2000/archive/2017/07/31/cefharp-wpf-osr-ime.html
-Advertisement-
Play Games

首先,本文所有 代碼已經提交到github,需要的可以直接從github獲取:https://github.com/starts2000/CefSharp,希望可以幫助到有需要的朋友們。 CEF 簡介 CEF is a BSD-licensed open source project founded ...


首先,本文所有 代碼已經提交到github,需要的可以直接從github獲取:https://github.com/starts2000/CefSharp,希望可以幫助到有需要的朋友們。

CEF 簡介

CEF is a BSD-licensed open source project founded by Marshall Greenblatt in 2008 and based on the Google Chromium project. Unlike the Chromium project itself, which focuses mainly on Google Chrome application development, CEF focuses on facilitating embedded browser use cases in third-party applications. CEF insulates the user from the underlying Chromium and Blink code complexity by offering production-quality stable APIs, release branches tracking specific Chromium releases, and binary distributions. Most features in CEF have default implementations that provide rich functionality while requiring little or no integration work from the user. There are currently over 100 million installed instances of CEF around the world embedded in products from a wide range of companies and industries. A partial list of companies and products using CEF is available on the CEF Wikipedia page. Some use cases for CEF include:

  • Embedding an HTML5-compliant Web browser control in an existing native application.
  • Creating a light-weight native “shell” application that hosts a user interface developed primarily using Web technologies.
  • Rendering Web content “off-screen” in applications that have their own custom drawing frameworks.
  • Acting as a host for automated testing of existing Web properties and applications.

CEF supports a wide range of programming languages and operating systems and can be easily integrated into both new and existing applications. It was designed from the ground up with both performance and ease of use in mind. The base framework includes C and C++ programming interfaces exposed via native libraries that insulate the host application from Chromium and Blink implementation details. It provides close integration between the browser and the host application including support for custom plugins, protocols, JavaScript objects and JavaScript extensions. The host application can optionally control resource loading, navigation, context menus, printing and more, while taking advantage of the same performance and HTML5 technologies available in the Google Chrome Web browser.
Numerous individuals and organizations contribute time and resources to support CEF development, but more involvement from the community is always welcome. This includes support for both the core CEF project and external projects that integrate CEF with additional programming languages and frameworks (see the "External Projects" section below). If you are interested in donating time to help with CEF development please see the "Helping Out" section below. If you are interested in donating money to support general CEF development and infrastructure efforts please visit the CEF Donations page.

CEF 的 .NET 開源項目主要有下麵三個:

  1. CefSharp:https://github.com/chillitom/CefSharp
  2. cefglue:https://bitbucket.org/xilium/xilium.cefglue
  3. chromiumfx:https://bitbucket.org/chromiumfx/chromiumfx

CEF osr IME BUG 歷史

CefSharp 和 cefglue 都使用 C++/CLI  對 cef API 進行封裝,都提供了 cef 的 Winform 和 WPF 控制項,而 chromiumfx 使用 P/Invoke 對 cef API 進行封裝,只提供了cef Winform 控制項。CefSharp 和 cefglue 的  cef WPF 控制項都使用 cef 的 osr ( off screen  render)方式進行渲染,由於 osr 模式一直存在 IME BUG,所以 CefSharp 和 cefglue 的 WPF 控制項也存在。

CEF osr IME bug 在 2012-11-22 就有人提出:https://bitbucket.org/chromiumembedded/cef/issues/798/out-of-focus-while-entering-ime,但是直到2016年底才解決https://bitbucket.org/chromiumembedded/cef/issues/1675/inline-ime-support-nstextinput-protocol-in,真是等的黃花菜都涼了。然而, CefSharp 和 cefglue 更是沒能跟上 CEF 的腳步,這個 BUG 直到現在也沒有解決,所有相關的 issue,回答都是建議在 WPF 中使用 Host WinForm 控制項的方式使用 CEF。

CEF osr IME bug:

 

最近通過參考 cef 的 osr 示例的源碼,通過移植和修改,終於實現了 CefSharp WPF 控制項的 IME 輸入,在這裡分享給大家。

主要是在 CefSharp.Core 項目中增加了對 IME 消息及 CEF IME 相關的處理,還有就是 WPF 的 ChromiumWebBrowser 控制項的相關代碼修改。

1.OsrImeHandler.cpp

// Copyright 2016 The Chromium Embedded Framework Authors. Portions copyright
// 2013 The Chromium Authors. All rights reserved. Use of this source code is
// governed by a BSD-style license that can be found in the LICENSE file.

// Implementation based on ui/base/ime/win/imm32_manager.cc from Chromium.

#include "Stdafx.h"
//#include <windowsx.h>
//#include <msctf.h>

#include "include/base/cef_build.h"
#include "OsrImeHandler.h"

#pragma comment(lib, "imm32.lib")

#define ColorUNDERLINE  0xFF000000    // Black SkColor value for underline,
// same as Blink.
#define ColorBKCOLOR    0x00000000    // White SkColor value for background,
// same as Blink.

namespace CefSharp {

    namespace {

        // Determines whether or not the given attribute represents a selection
        bool IsSelectionAttribute(char attribute) {
            return (attribute == ATTR_TARGET_CONVERTED ||
                attribute == ATTR_TARGET_NOTCONVERTED);
        }

        // Helper function for OsrImeHandler::GetCompositionInfo() method,
        // to get the target range that's selected by the user in the current
        // composition string.
        void GetCompositionSelectionRange(HIMC imc, int* target_start,
            int* target_end) {
            int attribute_size = ::ImmGetCompositionString(imc, GCS_COMPATTR, NULL, 0);
            if (attribute_size > 0) {
                int start = 0;
                int end = 0;
                std::vector<char> attribute_data(attribute_size);

                ::ImmGetCompositionString(imc, GCS_COMPATTR, &attribute_data[0],
                    attribute_size);
                for (start = 0; start < attribute_size; ++start) {
                    if (IsSelectionAttribute(attribute_data[start]))
                        break;
                }
                for (end = start; end < attribute_size; ++end) {
                    if (!IsSelectionAttribute(attribute_data[end]))
                        break;
                }

                *target_start = start;
                *target_end = end;
            }
        }

        // Helper function for OsrImeHandler::GetCompositionInfo() method, to get
        // underlines information of the current composition string.
        void GetCompositionUnderlines(
            HIMC imc,
            int target_start,
            int target_end,
            std::vector<CefCompositionUnderline> &underlines) {
            int clause_size = ::ImmGetCompositionString(imc, GCS_COMPCLAUSE, NULL, 0);
            int clause_length = clause_size / sizeof(uint32);
            if (clause_length) {
                std::vector<uint32> clause_data(clause_length);

                ::ImmGetCompositionString(imc, GCS_COMPCLAUSE,
                    &clause_data[0], clause_size);
                for (int i = 0; i < clause_length - 1; ++i) {
                    cef_composition_underline_t underline;
                    underline.range.from = clause_data[i];
                    underline.range.to = clause_data[i + 1];
                    underline.color = ColorUNDERLINE;
                    underline.background_color = ColorBKCOLOR;
                    underline.thick = 0;

                    // Use thick underline for the target clause.
                    if (underline.range.from >= target_start &&
                        underline.range.to <= target_end) {
                        underline.thick = 1;
                    }
                    underlines.push_back(underline);
                }
            }
        }

    }  // namespace

    OsrImeHandler::OsrImeHandler(HWND hwnd)
        : ime_status_(false),
        hwnd_(hwnd),
        input_language_id_(LANG_USER_DEFAULT),
        is_composing_(false),
        cursor_index_(-1),
        system_caret_(false) {
        ime_rect_ = { -1, -1, 0, 0 };
    }

    OsrImeHandler::~OsrImeHandler() {
        DestroyImeWindow();
    }

    void OsrImeHandler::SetInputLanguage() {
        // Retrieve the current input language from the system's keyboard layout.
        // Using GetKeyboardLayoutName instead of GetKeyboardLayout, because
        // the language from GetKeyboardLayout is the language under where the
        // keyboard layout is installed. And the language from GetKeyboardLayoutName
        // indicates the language of the keyboard layout itself.
        // See crbug.com/344834.
        WCHAR keyboard_layout[KL_NAMELENGTH];
        if (::GetKeyboardLayoutNameW(keyboard_layout)) {
            input_language_id_ =
                static_cast<LANGID>(_wtoi(&keyboard_layout[KL_NAMELENGTH >> 1]));
        } else {
            input_language_id_ = 0x0409;  // Fallback to en-US.
        }
    }

    void OsrImeHandler::CreateImeWindow() {
        // Chinese/Japanese IMEs somehow ignore function calls to
        // ::ImmSetCandidateWindow(), and use the position of the current system
        // caret instead -::GetCaretPos().
        // Therefore, we create a temporary system caret for Chinese IMEs and use
        // it during this input context.
        // Since some third-party Japanese IME also uses ::GetCaretPos() to determine
        // their window position, we also create a caret for Japanese IMEs.
        if (PRIMARYLANGID(input_language_id_) == LANG_CHINESE ||
            PRIMARYLANGID(input_language_id_) == LANG_JAPANESE) {
            if (!system_caret_) {
                if (::CreateCaret(hwnd_, NULL, 1, 1))
                    system_caret_ = true;
            }
        }
    }

    void OsrImeHandler::DestroyImeWindow() {
        // Destroy the system caret if we have created for this IME input context.
        if (system_caret_) {
            ::DestroyCaret();
            system_caret_ = false;
        }
    }

    void OsrImeHandler::MoveImeWindow() {
        // Does nothing when the target window has no input focus.
        if (GetFocus() != hwnd_)
            return;

        CefRect rc = ime_rect_;
        int location = cursor_index_;

        // If location is not specified fall back to the composition range start.
        if (location == -1)
            location = composition_range_.from;

        // Offset location by the composition range start if required.
        if (location >= composition_range_.from)
            location -= composition_range_.from;

        if (location < static_cast<int>(composition_bounds_.size()))
            rc = composition_bounds_[location];
        else
            return;

        HIMC imc = ::ImmGetContext(hwnd_);

        if (imc) {
            const int kCaretMargin = 1;
            if (PRIMARYLANGID(input_language_id_) == LANG_CHINESE) {
                // Chinese IMEs ignore function calls to ::ImmSetCandidateWindow()
                // when a user disables TSF (Text Service Framework) and CUAS (Cicero
                // Unaware Application Support).
                // On the other hand, when a user enables TSF and CUAS, Chinese IMEs
                // ignore the position of the current system caret and use the
                // parameters given to ::ImmSetCandidateWindow() with its 'dwStyle'
                // parameter CFS_CANDIDATEPOS.
                // Therefore, we do not only call ::ImmSetCandidateWindow() but also
                // set the positions of the temporary system caret if it exists.
                /*CANDIDATEFORM candidate_position = {
                    0, CFS_CANDIDATEPOS, { rc.x, rc.y }, { 0, 0, 0, 0 }
                };
                ::ImmSetCandidateWindow(imc, &candidate_position);*/

                COMPOSITIONFORM form = {
                    CFS_POINT,{ rc.x, rc.y },{ rc.x, rc.y, rc.x + rc.width, rc.y + rc.height }
                };

                auto ret = ::ImmSetCompositionWindow(imc, &form);
            }

            if (system_caret_) {
                switch (PRIMARYLANGID(input_language_id_)) {
                case LANG_JAPANESE:
                    ::SetCaretPos(rc.x, rc.y + rc.height);
                    break;
                default:
                    ::SetCaretPos(rc.x, rc.y);
                    break;
                }
            }

            if (PRIMARYLANGID(input_language_id_) == LANG_KOREAN) {
                // Korean IMEs require the lower-left corner of the caret to move their
                // candidate windows.
                rc.y += kCaretMargin;
            }

            COMPOSITIONFORM form = {
                CFS_RECT,{ rc.x, rc.y },{ rc.x, rc.y, rc.x + rc.width, rc.y + rc.height }
            };

            auto ret = ::ImmSetCompositionWindow(imc, &form);
            Debug::Assert(ret != 0);
            //Debug::WriteLine("=======ImmSetCompositionWindow:{0}, Rc:[{1},{2}]", ret != 0, rc.x, rc.y);

            // Japanese IMEs and Korean IMEs also use the rectangle given to
            // ::ImmSetCandidateWindow() with its 'dwStyle' parameter CFS_EXCLUDE
            // Therefore, we also set this parameter here.
            //CANDIDATEFORM exclude_rectangle = {
            //    0, CFS_EXCLUDE, { rc.x, rc.y },
            //    { rc.x, rc.y, rc.x + rc.width, rc.y + rc.height }
            //};

            //auto ret = ::ImmSetCandidateWindow(imc, &exclude_rectangle);
            //Debug::WriteLine("=======ImmSetCandidateWindow:{0}, Rc:[{1},{2}]", ret != 0, rc.x, rc.y);
            ::ImmReleaseContext(hwnd_, imc);
        }
    }

    void OsrImeHandler::CleanupComposition() {
        // Notify the IMM attached to the given window to complete the ongoing
        // composition (when given window is de-activated while composing and
        // re-activated) and reset the composition status.
        if (is_composing_) {
            HIMC imc = ::ImmGetContext(hwnd_);
            if (imc) {
                ::ImmNotifyIME(imc, NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
                ::ImmReleaseContext(hwnd_, imc);
            }
            ResetComposition();
        }
    }

    void OsrImeHandler::ResetComposition() {
        // Reset the composition status.
        is_composing_ = false;
        cursor_index_ = -1;
    }


    void OsrImeHandler::GetCompositionInfo(
        HIMC imc,
        LPARAM lparam,
        CefString &composition_text,
        std::vector<CefCompositionUnderline> &underlines,
        int& composition_start) {
        // We only care about GCS_COMPATTR, GCS_COMPCLAUSE and GCS_CURSORPOS, and
        // convert them into underlines and selection range respectively.
        underlines.clear();

        int length = static_cast<int>(composition_text.length());

        // Find out the range selected by the user.
        int target_start = length;
        int target_end = length;
        if (lparam & GCS_COMPATTR)
            GetCompositionSelectionRange(imc, &target_start, &target_end);

        // Retrieve the selection range information. If CS_NOMOVECARET is specified
        // it means the cursor should not be moved and we therefore place the caret at
        // the beginning of the composition string. Otherwise we should honour the
        // GCS_CURSORPOS value if it's available.
        // TODO(suzhe): Due to a bug in WebKit we currently can't use selection range
        // with composition string.
        // See: https://bugs.webkit.org/show_bug.cgi?id=40805
        if (!(lparam & CS_NOMOVECARET) && (lparam & GCS_CURSORPOS)) {
            // IMM32 does not support non-zero-width selection in a composition. So
            // always use the caret position as selection range.
            int cursor = ::ImmGetCompositionString(imc, GCS_CURSORPOS, NULL, 0);
            composition_start = cursor;
        } else {
            composition_start = 0;
        }

        // Retrieve the clause segmentations and convert them to underlines.
        if (lparam & GCS_COMPCLAUSE)
            GetCompositionUnderlines(imc, target_start, target_end, underlines);

        // Set default underlines in case there is no clause information.
        if (!underlines.size()) {
            CefCompositionUnderline underline;
            underline.color = ColorUNDERLINE;
            underline.background_color = ColorBKCOLOR;
            if (target_start > 0) {
                underline.range.from = 0;
                underline.range.to = target_start;
                underline.thick = 0;
                underlines.push_back(underline);
            }
            if (target_end > target_start) {
                underline.range.from = target_start;
                underline.range.to = target_end;
                underline.thick = 1;
                underlines.push_back(underline);
            }
            if (target_end < length) {
                underline.range.from = target_end;
                underline.range.to = length;
                underline.thick = 0;
                underlines.push_back(underline);
            }
        }
    }

    bool OsrImeHandler::GetString(HIMC imc, WPARAM lparam, int type,
        CefString& result) {
        if (!(lparam & type))
            return false;
        LONG string_size = ::ImmGetCompositionString(imc, type, NULL, 0);
        if (string_size <= 0)
            return false;

        // For trailing NULL - ImmGetCompositionString excludes that.
        string_size += sizeof(WCHAR);

        std::vector<wchar_t> buffer(string_size);
        ::ImmGetCompositionString(imc, type, &buffer[0], string_size);
        result.FromWString(&buffer[0]);
        return true;
    }

    bool OsrImeHandler::GetResult(LPARAM lparam, CefString& result) {
        bool ret = false;
        HIMC imc = ::ImmGetContext(hwnd_);
        if (imc) {
            ret = GetString(imc, lparam, GCS_RESULTSTR, result);
            ::ImmReleaseContext(hwnd_, imc);
        }
        return ret;
    }

    bool OsrImeHandler::GetComposition(
        LPARAM lparam,
        CefString &composition_text,
        std::vector<CefCompositionUnderline> &underlines,
        int& composition_start) {
        bool ret = false;
        HIMC imc = ::ImmGetContext(hwnd_);
        if (imc) {
            // Copy the composition string to the CompositionText object.
            ret = GetString(imc, lparam, GCS_COMPSTR, composition_text);

            if (ret) {
                // Retrieve the composition underlines and selection range information.
                GetCompositionInfo(imc, lparam, composition_text, underlines,
                    composition_start);

                // Mark that there is an ongoing composition.
                is_composing_ = true;
            }

            ::ImmReleaseContext(hwnd_, imc);
        }
        return ret;
    }

    void OsrImeHandler::DisableIME() {
        CleanupComposition();
        ::ImmAssociateContextEx(hwnd_, NULL, 0);
    }

    void OsrImeHandler::CancelIME() {
        if (is_composing_) {
            HIMC imc = ::ImmGetContext(hwnd_);
            if (imc) {
                ::ImmNotifyIME(imc, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
                ::ImmReleaseContext(hwnd_, imc);
            }
            ResetComposition();
        }
    }

    void OsrImeHandler::EnableIME() {
        // Load the default IME context.
        ::ImmAssociateContextEx(hwnd_, NULL, IACE_DEFAULT);
    }

    void OsrImeHandler::UpdateCaretPosition(int index) {
        // Save the caret position.
        cursor_index_ = index;
        // Move the IME window.
        MoveImeWindow();
    }

    void OsrImeHandler::ChangeCompositionRange(
        const CefRange& selection_range,
        const std::vector<CefRect>& bounds) {
        composition_range_ = selection_range;
        composition_bounds_ = bounds;
        MoveImeWindow();
    }

}  // namespace CefSharp

2、OsrImeWin.cpp 

#include "Stdafx.h"
#include "OsrImeWin.h"
#include "Internals\CefBrowserHostWrapper.h"

namespace CefSharp {
    OsrImeWin::OsrImeWin(IntPtr ownerHWnd, IBrowser^ browser) {
        _ownerHWnd = ownerHWnd;
        _browser = browser;
        _wndProcHandler = gcnew WndProcDelegate(this, &OsrImeWin::WndProc);

        _imeHandler = new OsrImeHandler(static_cast<HWND>(_ownerHWnd.ToPointer()));
    }

    OsrImeWin::~OsrImeWin() {
        _wndProcHandler = nullptr;
        _browser = nullptr;
        if (_imeHandler) {
            delete _imeHandler;
        }
    }

    void OsrImeWin::OnIMESetContext(UINT message, WPARAM wParam, LPARAM lParam) {
        // We handle the IME Composition Window ourselves (but let the IME Candidates
        // Window be handled by IME through DefWindowProc()), so clear the
        // ISC_SHOWUICOMPOSITIONWINDOW flag:
        lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
        ::DefWindowProc(static_cast<HWND>(_ownerHWnd.ToPointer()), message, wParam, lParam);

        // Create Caret Window if required
        if (_imeHandler) {
            _imeHandler->CreateImeWindow();
            _imeHandler->MoveImeWindow();
        }
    }

    void OsrImeWin::OnIMEStartComposition() {
        if (_imeHandler) {
            _imeHandler->CreateImeWindow();
            _imeHandler->MoveImeWindow();
            _imeHandler->ResetComposition();
        }
    }

    void OsrImeWin::OnIMEComposition(UINT message, WPARAM wParam, LPARAM lParam) {
        if (_browser && _imeHandler) {
            CefString cTextStr;
            if (_imeHandler->GetResult(lParam, cTextStr)) {
                // Send the text to the browser. The |replacement_range| and
                // |relative_cursor_pos| params are not used on Windows, so provide
                // default invalid values.

                auto host = safe_cast<CefBrowserHostWrapper^>(_browser->GetHost());
                //host->ImeCommitText(cTextStr)
                host->ImeCommitText(cTextStr,
                    CefRange(UINT32_MAX, UINT32_MAX), 0);
                _imeHandler->ResetComposition();
                // Continue reading the composition string - Japanese IMEs send both
                // GCS_RESULTSTR and GCS_COMPSTR.
            }

            std::vector<CefCompositionUnderline> underlines;
            int composition_start = 0;

            if (_imeHandler->GetComposition(lParam, cTextStr, underlines,
                composition_start)) {
                // Send the composition string to the browser. The |replacement_range|
                // param is not used on Windows, so provide a default invalid value.

                auto host = safe_cast<CefBrowserHostWrapper^>(_browser->GetHost());
                host->ImeSetComposition(cTextStr, underlines,
                    CefRange(UINT32_MAX, UINT32_MAX),
                    CefRange(composition_start,
                    static_cast<int>(composition_start + cTextStr.length())));

                // Update the Candidate Window position. The cursor is at the end so
                // subtract 1. This is safe because IMM32 does not support non-zero-width
                // in a composition. Also,  negative values are safely ignored in
                // MoveImeWindow
                _imeHandler->UpdateCaretPosition(composition_start - 1);
            } else {
                OnIMECancelCompositionEvent();
            }
        }
    }

    void OsrImeWin::OnIMECancelCompositionEvent() {
        if (_browser && _imeHandler) {
            _browser->GetHost()->ImeCancelComposition();
            _imeHandler->ResetComposition();
            _imeHandler->DestroyImeWindow();
        }
    }

    void OsrImeWin::OnImeCompositionRangeChanged(Range selectedRange, array<Rect>^ characterBounds) {
        if (_imeHandler) {
            // Convert from view coordinates to device coordinates.
            /*RectList device_bounds;
            CefRenderHandler::RectList::const_iterator it = character_bounds.begin();
            for (; it != character_bounds.end(); ++it) {
                device_bounds.push_back(LogicalToDevice(*it, device_scale_factor_));
            }*/

            std::vector<CefRect> device_bounds;
            for each(Rect rect in characterBounds) {
                CefRect rc(rect.X, rect.Y, rect.Width, rect.Height);
                device_bounds.push_back(rc);
            }

            CefRange selection_range(selectedRange.From, selectedRange.To);
            _imeHandler->ChangeCompositionRange(selection_range, device_bounds);
        }
    }

    void OsrImeWin::OnKeyEvent(int message, int wParam, int lParam) {
        if (!_browser)
            return;

        auto host = safe_cast<CefBrowserHostWrapper^>(_browser->GetHost());
        host->SendKeyEvent(message, wParam, lParam);
    }

    IntPtr OsrImeWin::WndProc(IntPtr hWnd, int message, IntPtr wParam, IntPtr lParam) {
        WPARAM pwParam;
        switch (message) {
            case WM_IME_SETCONTEXT:
                OnIMESetContext((UINT)message,
                    reinterpret_cast<WPARAM>(wParam.ToPointer()),
                    reinterpret_cast<LPARAM>(lParam.ToPointer()));
                return IntPtr::Zero;
            case WM_IME_STARTCOMPOSITION:
                OnIMEStartComposition();
                return IntPtr::Zero;
            case WM_IME_COMPOSITION:
                OnIMEComposition((UINT)message,
                    reinterpret_cast<WPARAM>(wParam.ToPointer()),
                    reinterpret_cast<LPARAM>(lParam.ToPointer()));
                return IntPtr::Zero;
            case WM_IME_ENDCOMPOSITION:
                OnIMECancelCompositionEvent();
                // Let WTL call::DefWindowProc() and release its resources.
                break;
            case WM_SYSCHAR:
            case WM_SYSKEYDOWN:
            case WM_SYSKEYUP:
            case WM_KEYDOWN:
            case WM_KEYUP:
            case WM_CHAR:
                OnKeyEvent(message, reinterpret_cast<int>(wParam.ToPointer()), reinterpret_cast<int>(lParam.ToPointer()));
                break;
        }

        return IntPtr(1);
    }
}

3、ChromiumWebBrow 修改請在 github 中查看。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一.環境:發行版本:ubuntu 14.04 64bit 二.獲取要指定的用戶及組id 使用id命令 (筆者獲取的uid和gid都為1000) 三.獲取識別的硬碟路徑 sudo fdisk -l (筆者獲取的路徑是/dev/sdb1) 四.掛載 sudo mount /dev/sdb1 /media ...
  • 說明:本文示例使用的VS2017和MVC5。 系統無論大小、牛逼或屌絲,一般都離不開註冊、登錄。那麼接下來我們就來分析下用戶身份認證。 簡單實現登錄、註銷 以前在學習.net的時候不知道什麼Forms身份認證,直接用session實現登錄,效果也蠻好嘛。而且用戶信息存在服務端,安全。 前端代碼: 後 ...
  • 之前調用 WebService 都是直接添加服務引用,然後調用 WebService 方法的,最近發現還可以使用 Http 請求調用 WebService。這裡還想說一句,還是 web api 的調用簡單。 WebService 服務端代碼: 很簡單的代碼,只是用於演示。 客戶端調用代碼: 第一種讀 ...
  • IdentityServer4 是一個提供 認證服務,單點登錄/登出(SSO),API訪問控制,聯合認證通道的可定製、免費商業支持的框架。 ...
  • 異常簡介 C sharp中的異常用於處理系統級和應用程式級的錯誤狀態,它是一種結構化、統一的類型安全的處理機制。異常處理相對於返回錯誤代碼的一個最大優點在於,異常可以被自動傳遞,這樣,在編程時異常更加難以被忽視。 C#的異常機制非常類似於C++的異常處理機制,但是還是有一些重要的區別: 1,在C#中 ...
  • 安裝過程就簡單帶過: 1. 在看代碼前,我們需要將NpgSql和Petapoco 載入到當前項目中來,我將使用Nuget來添加到當前項目,分別如下: 2.下麵看一下Web.config中的重要代碼 1>資料庫連接字元串 2>NpgSql驅動配置文件 3.將 Npgsql.dll和 Mono.Secu ...
  • nop支持多店,結合NivoSlider插件介紹下如何開發支持多商城的小部件 ...
  • IdentityServer4 是一個提供 認證服務,單點登錄/登出(SSO),API訪問控制,聯合認證通道的可定製、免費商業支持的框架。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...