zoukankan      html  css  js  c++  java
  • ASP.NET-Core-Web-API-Best-Practices-Guide

     ASP.NET-Core-Web-API-Best-Practices-Guide

    ASP.NET Core Web API 最佳实践指南

     

    原文地址: ASP.NET-Core-Web-API-Best-Practices-Guide

    介绍#

    当我们编写一个项目的时候,我们的主要目标是使它能如期运行,并尽可能地满足所有用户需求。

    但是,你难道不认为创建一个能正常工作的项目还不够吗?同时这个项目不应该也是可维护和可读的吗?

    事实证明,我们需要把更多的关注点放到我们项目的可读性和可维护性上。这背后的主要原因是我们或许不是这个项目的唯一编写者。一旦我们完成后,其他人也极有可能会加入到这里面来。

    因此,我们应该把关注点放到哪里呢?

    在这一份指南中,关于开发 .NET Core Web API 项目,我们将叙述一些我们认为会是最佳实践的方式。进而让我们的项目变得更好和更加具有可维护性。

    现在,让我们开始想一些可以应用到 ASP.NET Web API 项目中的一些最佳实践。

    Startup 类 和 服务配置#

    STARTUP CLASS AND THE SERVICE CONFIGURATION

    在 Startup 类中,有两个方法:ConfigureServices 是用于服务注册,Configure 方法是向应用程序的请求管道中添加中间件。

    因此,最好的方式是保持 ConfigureServices 方法简洁,并且尽可能地具有可读性。当然,我们需要在该方法内部编写代码来注册服务,但是我们可以通过使用 扩展方法 来让我们的代码更加地可读和可维护。

    例如,让我们看一个注册 CORS 服务的不好方式:

    Copy
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options => 
        {
            options.AddPolicy("CorsPolicy", builder => builder.AllowAnyOrigin()
                .AllowAnyMethod()
                .AllowAnyHeader()
                .AllowCredentials());
        });
    }

    尽管这种方式看起来挺好,也能正常地将 CORS 服务注册成功。但是想象一下,在注册了十几个服务之后这个方法体的长度。

    这样一点也不具有可读性。

    一种好的方式是通过在扩展类中创建静态方法:

    Copy
    public static class ServiceExtensions
    {
        public static void ConfigureCors(this IServiceCollection services)
        {
            services.AddCors(options =>
            {
                options.AddPolicy("CorsPolicy", builder => builder.AllowAnyOrigin()
                    .AllowAnyMethod()
                    .AllowAnyHeader()
                    .AllowCredentials());
            });
        }
    }

    然后,只需要调用这个扩展方法即可:

    Copy
    public void ConfigureServices(IServiceCollection services)
    {
        services.ConfigureCors();
    }

    了解更多关于 .NET Core 的项目配置,请查看:.NET Core Project Configuration

    项目组织#

    PROJECT ORGANIZATION

    我们应该尝试将我们的应用程序拆分为多个小项目。通过这种方式,我们可以获得最佳的项目组织方式,并能将关注点分离(SoC)。我们的实体、契约、访问数据库操作、记录信息或者发送邮件的业务逻辑应该始终放在单独的 .NET Core 类库项目中。

    应用程序中的每个小项目都应该包含多个文件夹用来组织业务逻辑。

    这里有个简单的示例用来展示一个复杂的项目应该如何组织:

    基于环境的设置#

    ENVIRONMENT BASED SETTINGS

    当我们开发应用程序时,它处于开发环境。但是一旦我们发布之后,它将处于生产环境。因此,将每个环境进行隔离配置往往是一种好的实践方式。

    在 .NET Core 中,这一点很容易实现。

    一旦我们创建好了项目,就已经有一个 appsettings.json 文件,当我们展开它时会看到 appsettings.Development.json 文件:

    此文件中的所有设置将用于开发环境。

    我们应该添加另一个文件 appsettings.Production.json,将其用于生产环境:

    生产文件将位于开发文件下面。

    设置修改后,我们就可以通过不同的 appsettings 文件来加载不同的配置,取决于我们应用程序当前所处环境,.NET Core 将会给我们提供正确的设置。更多关于这一主题,请查阅:Multiple Environments in ASP.NET Core.

    数据访问层#

    DATA ACCESS LAYER

    在一些不同的示例教程中,我们可能看到 DAL 的实现在主项目中,并且每个控制器中都有实例。我们不建议这么做。

    当我们编写 DAL 时,我们应该将其作为一个独立的服务来创建。在 .NET Core 项目中,这一点很重要,因为当我们将 DAL 作为一个独立的服务时,我们就可以将其直接注入到 IOC(控制反转)容器中。IOC 是 .NET Core 内置功能。通过这种方式,我们可以在任何控制器中通过构造函数注入的方式来使用。

    Copy
    public class OwnerController: Controller
    {
        private readonly IRepository _repository;
        public OwnerController(IRepository repository)
        {
            _repository = repository;
        }
    }

    控制器#

    CONTROLLERS

    控制器应该始终尽量保持整洁。我们不应该将任何业务逻辑放置于内。

    因此,我们的控制器应该通过构造函数注入的方式接收服务实例,并组织 HTTP 的操作方法(GET,POST,PUT,DELETE,PATCH...):

    Copy
    public class OwnerController : Controller
    {
        private readonly ILoggerManager _logger;
        private readonly IRepository _repository;
        public OwnerController(ILoggerManager logger, IRepository repository)
        {
            _logger = logger;
            _repository = repository;
        }
    
        [HttpGet]
        public IActionResult GetAllOwners()
        {
        }
        [HttpGet("{id}", Name = "OwnerById")]
        public IActionResult GetOwnerById(Guid id)
        {
        }
        [HttpGet("{id}/account")]
        public IActionResult GetOwnerWithDetails(Guid id)
        {
        }
        [HttpPost]
        public IActionResult CreateOwner([FromBody]Owner owner)
        {
        }
        [HttpPut("{id}")]
        public IActionResult UpdateOwner(Guid id, [FromBody]Owner owner)
        {
        }
        [HttpDelete("{id}")]
        public IActionResult DeleteOwner(Guid id)
        {
        }
    }

    我们的 Action 应该尽量保持简洁,它们的职责应该包括处理 HTTP 请求,验证模型,捕捉异常和返回响应。

    Copy
    [HttpPost]
    public IActionResult CreateOwner([FromBody]Owner owner)
    {
        try
        {
            if (owner.IsObjectNull())
            {
                return BadRequest("Owner object is null");
            }
            if (!ModelState.IsValid)
            {
                return BadRequest("Invalid model object");
            }
            _repository.Owner.CreateOwner(owner);
            return CreatedAtRoute("OwnerById", new { id = owner.Id }, owner);
        }
        catch (Exception ex)
        {
            _logger.LogError($"Something went wrong inside the CreateOwner action: { ex} ");
            return StatusCode(500, "Internal server error");
        }
    }

    在大多数情况下,我们的 action 应该将 IActonResult 作为返回类型(有时我们希望返回一个特定类型或者是 JsonResult ...)。通过使用这种方式,我们可以很好地使用 .NET Core 中内置方法的返回值和状态码。

    使用最多的方法是:

    • OK => returns the 200 status code
    • NotFound => returns the 404 status code
    • BadRequest => returns the 400 status code
    • NoContent => returns the 204 status code
    • Created, CreatedAtRoute, CreatedAtAction => returns the 201 status code
    • Unauthorized => returns the 401 status code
    • Forbid => returns the 403 status code
    • StatusCode => returns the status code we provide as input

    处理全局异常#

    HANDLING ERRORS GLOBALLY

    在上面的示例中,我们的 action 内部有一个 try-catch 代码块。这一点很重要,我们需要在我们的 action 方法体中处理所有的异常(包括未处理的)。一些开发者在 action 中使用 try-catch 代码块,这种方式明显没有任何问题。但我们希望 action 尽量保持简洁。因此,从我们的 action 中删除 try-catch ,并将其放在一个集中的地方会是一种更好的方式。.NET Core 给我们提供了一种处理全局异常的方式,只需要稍加修改,就可以使用内置且完善的的中间件。我们需要做的修改就是在 Startup 类中修改 Configure 方法:

    Copy
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseExceptionHandler(config => 
        {
            config.Run(async context => 
            {
                context.Response.StatusCode = 500;
                context.Response.ContentType = "application/json";
    
                var error = context.Features.Get<IExceptionHandlerFeature>();
                if (error != null)
                {
                    var ex = error.Error;
                    await context.Response.WriteAsync(new ErrorModel
                    {
                        StatusCode = 500,
                        ErrorMessage = ex.Message
                    }.ToString());
                }
            });
        });
    
        app.UseRouting();
    
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }

    我们也可以通过创建自定义的中间件来实现我们的自定义异常处理:

    Copy
    // You may need to install the Microsoft.AspNetCore.Http.Abstractions package into your project
    public class CustomExceptionMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger<CustomExceptionMiddleware> _logger;
        public CustomExceptionMiddleware(RequestDelegate next, ILogger<CustomExceptionMiddleware> logger)
        {
            _next = next;
            _logger = logger;
        }
    
        public async Task Invoke(HttpContext httpContext)
        {
            try
            {
                await _next(httpContext);
            }
            catch (Exception ex)
            {
                _logger.LogError("Unhandled exception....", ex);
                await HandleExceptionAsync(httpContext, ex);
            }
        }
    
        private Task HandleExceptionAsync(HttpContext httpContext, Exception ex)
        {
            //todo
            return Task.CompletedTask;
        }
    }
    
    // Extension method used to add the middleware to the HTTP request pipeline.
    public static class CustomExceptionMiddlewareExtensions
    {
        public static IApplicationBuilder UseCustomExceptionMiddleware(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<CustomExceptionMiddleware>();
        }
    }

    之后,我们只需要将其注入到应用程序的请求管道中即可:

    Copy
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseCustomExceptionMiddleware();
    }

    使用过滤器移除重复代码#

    USING ACTIONFILTERS TO REMOVE DUPLICATED CODE

    ASP.NET Core 的过滤器可以让我们在请求管道的特定状态之前或之后运行一些代码。因此如果我们的 action 中有重复验证的话,可以使用它来简化验证操作。

    当我们在 action 方法中处理 PUT 或者 POST 请求时,我们需要验证我们的模型对象是否符合我们的预期。作为结果,这将导致我们的验证代码重复,我们希望避免出现这种情况,(基本上,我们应该尽我们所能避免出现任何代码重复。)我们可以在代码中通过使用 ActionFilter 来代替我们的验证代码:

    Copy
    if (!ModelState.IsValid)
    {
        //bad request and logging logic
    }

    我们可以创建一个过滤器:

    Copy
    public class ModelValidationAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            if (!context.ModelState.IsValid)
            {
                context.Result = new BadRequestObjectResult(context.ModelState);
            }
        }
    }

    然后在 Startup 类的 ConfigureServices 函数中将其注入:

    Copy
    services.AddScoped<ModelValidationAttribute>();

    现在,我们可以将上述注入的过滤器应用到我们的 action 中。

    Microsoft.AspNetCore.All 元包#

    MICROSOFT.ASPNETCORE.ALL META-PACKAGE

    注:如果你使用的是 2.1 和更高版本的 ASP.NET Core。建议使用 Microsoft.AspNetCore.App 包,而不是 Microsoft.AspNetCore.All。这一切都是出于安全原因。此外,如果使用 2.1 版本创建新的 WebAPI 项目,我们将自动获取 AspNetCore.App 包,而不是 AspNetCore.All。

    这个元包包含了所有 AspNetCore 的相关包,EntityFrameworkCore 包,SignalR 包(version 2.1) 和依赖框架运行的支持包。采用这种方式创建一个新项目很方便,因为我们不需要手动安装一些我们可能使用到的包。

    当然,为了能使用 Microsoft.AspNetCore.all 元包,需要确保你的机器安装了 .NET Core Runtime。

    路由#

    ROUTING

    在 .NET Core Web API 项目中,我们应该使用属性路由代替传统路由,这是因为属性路由可以帮助我们匹配路由参数名称与 Action 内的实际参数方法。另一个原因是路由参数的描述,对我们而言,一个名为 "ownerId" 的参数要比 "id" 更加具有可读性。

    我们可以使用 [Route] 属性来在控制器的顶部进行标注:

    Copy
    [Route("api/[controller]")]
    public class OwnerController : Controller
    {
        [Route("{id}")]
        [HttpGet]
        public IActionResult GetOwnerById(Guid id)
        {
        }
    }

    还有另一种方式为控制器和操作创建路由规则:

    Copy
    [Route("api/owner")]
    public class OwnerController : Controller
    {
        [Route("{id}")]
        [HttpGet]
        public IActionResult GetOwnerById(Guid id)
        {
        }
    }

    对于这两种方式哪种会好一些存在分歧,但是我们经常建议采用第二种方式。这是我们一直在项目中采用的方式。

    当我们谈论路由时,我们需要提到路由的命名规则。我们可以为我们的操作使用描述性名称,但对于 路由/节点,我们应该使用 NOUNS 而不是 VERBS。

    一个较差的示例:

    Copy
    [Route("api/owner")]
    public class OwnerController : Controller
    {
        [HttpGet("getAllOwners")]
        public IActionResult GetAllOwners()
        {
        }
        [HttpGet("getOwnerById/{id}"]
        public IActionResult GetOwnerById(Guid id)
        {
        }
    }

    一个较好的示例:

    Copy
    [Route("api/owner")]
    public class OwnerController : Controller
    {
        [HttpGet]
        public IActionResult GetAllOwners()
        {
        }
        [HttpGet("{id}"]
        public IActionResult GetOwnerById(Guid id)
        {
        }
    }
    

    更多关于 Restful 实践的细节解释,请查阅:Top REST API Best Practices

    日志#

    LOGGING

    如果我们打算将我们的应用程序发布到生产环境,我们应该在合适的位置添加一个日志记录机制。在生产环境中记录日志对于我们梳理应用程序的运行很有帮助。

    .NET Core 通过继承 ILogger 接口实现了它自己的日志记录。通过借助依赖注入机制,它可以很容易地使用。

    Copy
    public class TestController: Controller
    {
        private readonly ILogger _logger;
        public TestController(ILogger<TestController> logger)
        {
            _logger = logger;
        }
    }

    然后,在我们的 action 中,我们可以通过使用 _logger 对象借助不同的日志级别来记录日志。

    .NET Core 支持使用于各种日志记录的 Provider。因此,我们可能会在项目中使用不同的 Provider 来实现我们的日志逻辑。

    NLog 是一个很不错的可以用于我们自定义的日志逻辑类库,它极具扩展性。支持结构化日志,且易于配置。我们可以将信息记录到控制台,文件甚至是数据库中。

    想了解更多关于该类库在 .NET Core 中的应用,请查阅:.NET Core series – Logging With NLog.

    Serilog 也是一个很不错的类库,它适用于 .NET Core 内置的日志系统。

    这里有一个能提高日志性能的小技巧:字符串拼接建议使用 _logger.LogInformation("{0},{1}", DateTime.Now, "info") 方式来记录日志,而不是 _logger.LogInformation($"{DateTime.Now},info")

    加密#

    CRYPTOHELPER

    我们不会建议将密码以明文形式存储到数据库中。处于安全原因,我们需要对其进行哈希处理。这超出了本指南的内容范围。互联网上有大量哈希算法,其中不乏一些不错的方法来将密码进行哈希处理。

    但是如果需要为 .NET Core 的应用程序提供易于使用的加密类库,CryptoHelper 是一个不错的选择。

    CryptoHelper 是适用于 .NET Core 的独立密码哈希库,它是基于 PBKDF2 来实现的。通过创建 Data Protection 栈来将密码进行哈希化。这个类库在 NuGet 上是可用的,并且使用也很简单:

    Copy
    using CryptoHelper;
    
    // Hash a password
    public string HashPassword(string password)
    {
        return Crypto.HashPassword(password);
    }
    
    // Verify the password hash against the given password
    public bool VerifyPassword(string hash, string password)
    {
        return Crypto.VerifyHashedPassword(hash, password);
    }

    内容协商#

    CONTENT NEGOTIATION

    默认情况下,.NET Core Web API 会返回 JSON 格式的结果。大多数情况下,这是我们所希望的。

    但是如果客户希望我们的 Web API 返回其它的响应格式,例如 XML 格式呢?

    为了解决这个问题,我们需要进行服务端配置,用于按需格式化我们的响应结果:

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

    但有时客户端会请求一个我们 Web API 不支持的格式,因此最好的实践方式是对于未经处理的请求格式统一返回 406 状态码。这种方式也同样能在 ConfigureServices 方法中进行简单配置:

    Copy
    public void ConfigureServices(IServiceCollection services)
    {
         services.AddControllers(option => option.ReturnHttpNotAcceptable = true).AddXmlDataContractSerializerFormatters();
    }

    我们也可以创建我们自己的格式化规则。

    这一部分内容是一个很大的主题,如果你希望了解更多,请查阅:Content Negotiation in .NET Core

    使用 JWT#

    USING JWT

    现如今的 Web 开发中,JSON Web Tokens (JWT) 变得越来越流行。得益于 .NET Core 内置了对 JWT 的支持,因此实现起来非常容易。JWT 是一个开发标准,它允许我们以 JSON 格式在服务端和客户端进行安全的数据传输。

    我们可以在 ConfigureServices 中配置 JWT 认证:

    Copy
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options => 
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidIssuer = _authToken.Issuer,
    
                    ValidateAudience = true,
                    ValidAudience = _authToken.Audience,
    
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_authToken.Key)),
    
                    RequireExpirationTime = true,
                    ValidateLifetime = true,
    
                    //others
                };
            });
    }

    为了能在应用程序中使用它,我们还需要在 Configure 中调用下面一段代码:

    Copy
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseAuthentication();
    }

    此外,创建 Token 可以使用如下方式:

    Copy
    var securityToken = new JwtSecurityToken(
                    claims: new Claim[]
                    {
                        new Claim(ClaimTypes.NameIdentifier,user.Id),
                        new Claim(ClaimTypes.Email,user.Email)
                    },
                    issuer: _authToken.Issuer,
                    audience: _authToken.Audience,
                    notBefore: DateTime.Now,
                    expires: DateTime.Now.AddDays(_authToken.Expires),
                    signingCredentials: new SigningCredentials(
                        new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_authToken.Key)),
                        SecurityAlgorithms.HmacSha256Signature));
    
    Token = new JwtSecurityTokenHandler().WriteToken(securityToken)

    基于 Token 的用户验证可以在控制器中使用如下方式:

    Copy
    var auth = await HttpContext.AuthenticateAsync();
    var id = auth.Principal.Claims.FirstOrDefault(x => x.Type.Equals(ClaimTypes.NameIdentifier))?.Value;

    我们也可以将 JWT 用于授权部分,只需添加角色声明到 JWT 配置中即可。

    更多关于 .NET Core 中 JWT 认证和授权部分,请查阅:authentication-aspnetcore-jwt-1 和 authentication-aspnetcore-jwt-2

    总结#

    读到这里,可能会有朋友对上述一些最佳实践不是很认同,因为全篇都没有谈及更切合项目的实践指南,比如 TDD 、DDD 等。但我个人认为上述所有的最佳实践是基础,只有把这些基础掌握了,才能更好地理解一些更高层次的实践指南。万丈高楼平地起,所以你可以把这看作是一篇面向新手的最佳实践指南。

    在这份指南中,我们的主要目的是让你熟悉关于使用 .NET Core 开发 web API 项目时的一些最佳实践。这里面的部分内容在其它框架中也同样适用。因此,熟练掌握它们很有用。

    非常感谢你能阅读这份指南,希望它能对你有所帮助。

    Github 更新地址:ASP.NET Core Web API 最佳实践

    作者: hippieZhou

    出处:https://www.cnblogs.com/hippieZhou/p/11966373.html

    版权:本文采用「署名-非商业性使用-相同方式共享 4.0 国际」知识共享许可协议进行许可。

    随笔分类 - 0x02 .NET Core

     
    摘要:前言 我们知道,目前大多数应用程序在正式发布到生产环境之前都会经历多个不同的测试环境,通过让应用程序在多个不同的环境中运行来及时发现并解决问题,避免在线上发生不必要的损失。这是对于整个软件的发布流程来讲。但是如果想让我们的应用程序在线上环境中通过满足一些动态条件(比如电商平台在某一时间段的促销活动)阅读全文
    posted @ 2020-01-02 09:52 hippieZhou 阅读 (946) | 评论 (8) 编辑
     
    摘要:原文地址: "ASP.NET Core Web API Best Practices Guide" 介绍 当我们编写一个项目的时候,我们的主要目标是使它能如期运行,并尽可能地满足所有用户需求。 但是,你难道不认为创建一个能正常工作的项目还不够吗?同时这个项目不应该也是可维护和可读的吗? 事实证明,我阅读全文
    posted @ 2019-12-19 09:59 hippieZhou 阅读 (5924) | 评论 (31) 编辑
     
    摘要:背景 目前我主要负责的一个项目是一个 C/S 架构的客户端开发,前端主要是通过 相关技术来实现,后端是通过 来实现,前后端的数据通信则是通过 的方式来进行处理。由于 进程是需要依赖客户端进程来运行,为了保证后端业务进程的稳定性,就需要通过一个 来守护 Python 进程,防止其由于未知原因而出现进程阅读全文
    posted @ 2019-09-28 11:51 hippieZhou 阅读 (979) | 评论 (0) 编辑
     
    摘要:前言 在 ASP.NET Core 中,微软提供了一套默认的依赖注入实现,该实现对应的包为: ,我们可以通过查看其对应的开源仓库看一下它的具体实现。基于该实现,我们不必显式创建我们的服务对象,可以将其统一注入到 ServiceProvider 中进行集中维护,使用的时候直接在该对象中获取即可。让我们阅读全文
    posted @ 2019-08-28 09:19 hippieZhou 阅读 (1131) | 评论 (12) 编辑
     
    摘要:背景描述 最近接到一个需求,就是要求我们的 WPF 客户端具备本地化功能,实现中英文多语言界面。刚开始接到这个需求,其实我内心是拒绝的的,但是没办法,需求是永无止境的。所以只能想办法解决这个问题。 首先有必要说一下我们的系统架构。我们的系统是基于 Prism 来进行设计的,所以每个业务模块之间都是相阅读全文
    posted @ 2019-08-13 09:13 hippieZhou 阅读 (1199) | 评论 (11) 编辑
     
    摘要:首先,很感谢在上篇文章 "C 管道式编程" 中给我有小额捐助和点赞的朋友们,感谢你们的支持与肯定。希望我的每一次分享都能让彼此获得一些收获,当然如果我有些地方叙述的不正确或不当,还请不客气的指出。好了,下面进入正文。 前言 在开始之前,我们需要明确的一个概念是,在 Web 程序中,用户的每次请求流程阅读全文
    posted @ 2019-07-25 07:58 hippieZhou 阅读 (3365) | 评论 (11) 编辑
     
    摘要:受 F 中的管道运算符和 C 中的 LINQ 语法,管道式编程为 C 提供了更加灵活性的功能性编程。通过使用 扩展函数 可以将多个功能连接起来构建成一个管道。 前言 在 C 编程中,管道式编程(Pipeline Style programming)其实存在已久,最明显的就是我们经常使用的 LINQ。阅读全文
    posted @ 2019-07-17 10:18 hippieZhou 阅读 (5166) | 评论 (24) 编辑
     
    摘要:问题描述 在传统的基于 .NET Framework 的 WPF 程序中,我们可以使用如下代码段启动相关的默认应用: 但是上述协议方式在 .NET Core 中不再适用,当我们使用上述方式进行操作,程序会给我们爆如下的错误: 经 "神樹桜乃" 大佬提醒,我特意看了一下 ProcessStartInf阅读全文
    posted @ 2019-04-21 13:11 hippieZhou 阅读 (1237) | 评论 (6) 编辑
     
    摘要:本文对应的原文来至 c sharpcorner 的一篇文章,文末有链接。如有错误,还请指正。 前言 你会为你的下一个应用程序选择哪一种开发平台 .NET Framework 或者 .NET Core?在这篇文章中,让我们比较一下这两个开发平台的特点,看能否得出一个结论。 .NET Framework阅读全文
    posted @ 2019-04-19 08:03 hippieZhou 阅读 (2280) | 评论 (8) 编辑
     
    摘要:如何你希望你的 WPF 程序能够以 Windows 的保护机制保护起来,不被轻易反编译的话,那么这篇文章应该能帮到你。 介绍 MSIX 是微软于去年的 Windows 开发者日峰会 上推出的全新应用打包解决方案。其目的是取代旧式的软件打包方式,可用于 Win32、WindowsForm 、 WPF阅读全文
    posted @ 2019-04-17 08:02 hippieZhou 阅读 (2232) | 评论 (4) 编辑
     
    摘要:描述 当我们安装完 DotNetCore 3.0 版本的 SDK 后,我们就可以创建基于 DotNetCore 的 WPF 项目模板,通过如下 CLI 可以方便快捷的创建并运行我们的项目: 做过 WPF 开发的朋友都知道,这个项目模板肯定不符合我们的预期,我们希望我们的项目模板能够加入 MVVM 的阅读全文
    posted @ 2019-04-15 08:13 hippieZhou 阅读 (580) | 评论 (0) 编辑
     
    摘要:介绍 由于历史原因,基于 Windows 平台存在着大量的基于 .NetFramework 开发的 WPF 和 WinForm 相关程序,如果将这些程序全部基于 DotNetCore 3.0 重写一遍显然是不现实的,但是 DotNetCore 是未来发展的趋势。所以本文通过以 WPF 为例,介绍一下阅读全文
    posted @ 2019-04-11 07:49 hippieZhou 阅读 (3703) | 评论 (33) 编辑
     
    摘要:DotNetCore Is AnyWhere. 前言 Visual Studio 2019 已经正式发布了, DotNetCore 3.0 的正式版也指日可待。在之前的版本中,作为一名基于微软生态的传统 WPF 程序员看着隔壁同学在开发 DotNetCore 网站时用着各种特性好生羡慕,想着巨硬啥时阅读全文
    posted @ 2019-04-04 08:04 hippieZhou 阅读 (8100) | 评论 (62) 编辑
     
    摘要:Server 端示例代码: Client 端示例代码: It supports: WebSocket Client and Server RFC 6455 Per message Compression extension Secure Connection HTTP Authentication阅读全文
    posted @ 2018-11-02 20:38 hippieZhou 阅读 (777) | 评论 (4) 编辑
     
    摘要:问题描述 最近在进行业务扩展时,我发现我之前封装的 DialogServie 问题越来越多,整个设计思路一点也不 SOLID 。这里我简单描述一下: DialogServie 采用单例模式。内部定义了一个列表,用于存放当前系统所有打开的窗口实例,然后上层通过调用 方法来创建并显示一个窗口,调用 方法阅读全文
    posted @ 2018-11-02 20:23 hippieZhou 阅读 (506) | 评论 (0) 编辑
     
    摘要:基础环境配置 域名和服务器请先自行购买 基于 云服务器ECS 创建一个应用实例,选择系统镜像为 Ubuntu 16.04 ,在本机通过 SSH 进行远程连接,并进行相关配置 Nginx 配置 配置 default 文件,如下所示 检测配置并更新 安装 DotNetCore 请参考官网最新安装说明:阅读全文
    posted @ 2018-10-28 16:11 hippieZhou 阅读 (726) | 评论 (3) 编辑
     
    摘要:背景叙述 在前面几篇 MEF 插件式开发 系列博客中,我分别在 和 两种框架下实验了 MEF 的简单实验,由于 由来已久,因此基于该框架下衍生出的很多优秀的 MEF 框架较多。但是对于 来说,情况有所不同,由于它本身对 DI 内置并提供支持,因此我尝试使用它的全新 依赖注入(DI) 来做一些实验。阅读全文
    posted @ 2018-08-14 10:04 hippieZhou 阅读 (1183) | 评论 (2) 编辑
     
    摘要:背景叙述 在传统的基于 框架下进行的 MEF 开发,大多是使用 MEF 1 ,对应的命名空间是 System.ComponentModel.Composition 。在 中,微软为了伟大的跨平台策略,引入了 MEF 2 ,其对应的命名空间是 System.Composition ,这个需要开发者自己阅读全文
    posted @ 2018-08-13 09:57 hippieZhou 阅读 (1581) | 评论 (6) 编辑
     
    摘要:[TOC] C 编程指南 前不久在 Github 上看见了一位大牛创建一个仓库: "CSharpCodingGuidelines" ,打开之后看了一下 相关描述,感觉应该很不错,于是就 clone 到本地拜读一下,这里列一些自己的笔记,方便日后回顾。 基本原则 Astonishment 原则:你的代阅读全文
    posted @ 2018-08-05 19:27 hippieZhou 阅读 (406) | 评论 (4) 编辑
     
    摘要:[TOC] 目前 EF 是 .NET 平台下相当成熟的 ORM 框架,但是其最新发布的 6.x 版本依然不支持 SQLite 的 CodeFirst 模式,好在有大神已经在 Nuget 上发布的相应的 Package 来解决这个问题。笔者通过做一个小实验来验证一下。 问题描述 SQLite 本身不支阅读全文
    posted @ 2018-08-04 23:45 hippieZhou 阅读 (1166) | 评论 (0) 编辑
     
    摘要:MEF 在 WPF 中的简单应用 MEF 的开发模式主要适用于插件化的业务场景中,C/S 和 B/S 中都有相应的使用场景,其中包括但不限于 、 、`WPF UWP DotNet Core` 也是支持的。 在上篇文章中,笔者大致讲述如果在控制台程序中创建一个简单的 MEF 应用程序。如果有读者不太清阅读全文
    posted @ 2018-08-02 21:13 hippieZhou 阅读 (3087) | 评论 (8) 编辑
     
    摘要:MEF 简介 Managed Extensibility Framework 即 MEF 是用于创建轻量、可扩展应用程序的库。 它让应用程序开发人员得以发现和使用扩展且无需配置。 它还让扩展开发人员得以轻松地封装代码并避免脆弱的紧密依赖性。 MEF 让扩展不仅可在应用程序内重复使用,还可以跨程序重复阅读全文
    posted @ 2018-08-01 10:25 hippieZhou 阅读 (1603) | 评论 (2) 编辑
     
    摘要:已经有一段时间没有写博客来记录自己的学习点滴了。现在回想起来确实有些惭愧,期间经历了一些事情,到目前为止算是平息了,是时候该收收心来充实自己了。 在本篇缪文中,楼主打算给UWP开发的初学者讲述一个在开发中经常遇到的很现实的问题:页面回退逻辑 。 众所周知,UWP的应用程序理论上是可以运行在Windo阅读全文
    posted @ 2016-02-20 18:50 hippieZhou 阅读 (1276) | 评论 (18) 编辑
     
    摘要:众所周知,在UWP中,微软为我们提供了一种新的绑定方式:x:bind,它是基于编译时的绑定。在性能方面,运行时绑定Binding与它相比还是有些逊色的。因此针对一些确定的、不需要变更的数据,我们完全有理由来使用X:bind进行绑定。(当然,如果你不在乎程序性能的话就没必要继续往下看了!) 悉MVVM阅读全文
    posted @ 2015-11-05 20:23 hippieZhou 阅读 (805) | 评论 (1) 编辑
     
    摘要:一:首先来看一下UserControl 熟悉XAML的朋友们都知道,当我们创建一个用户控件的时候,VS会自动为我们生成一个XXX.xaml文件和XXX..xaml.cs文件,XAML文件用于进行控件的UI界面设计,对应的CS文件则是进行逻辑代码的编写。我们主要看一下CS文件,打开CS文件,我们会发现阅读全文
    posted @ 2015-10-15 23:13 hippieZhou 阅读 (473) | 评论 (2) 编辑
     
    摘要:其实写这篇博文的时候我是拒绝的,因为这牵扯到一个高大上的东西——"框架"。一说起这个东西,很多朋友就感觉有点蒙了,尤其是编程新手。因为它不像在代码里面定义一个变量那么显而易见,它是需要在你的整个程序架构上体现出来的,并且对于框架来说,并没有什么固定的代码格式,你可以这样写,当然也可以那样写。只要最终阅读全文
    posted @ 2015-09-15 09:06 hippieZhou 阅读 (2848) | 评论 (21) 编辑
     
    摘要:Windows10发布已经有一阵子了,已经有一些公司上架了自己的UWP应用程序,为WindowsStore增添光彩。已经安装Windows10的用户也或多或少的安装了一些UWP的应用程序,针对这些UWP的应用程序设计来说有好有坏,好的方面体现在它们的用户体验始终是保证一致,符合Win10的产品理念,阅读全文
    posted @ 2015-09-10 23:09 hippieZhou 阅读 (652) | 评论 (0) 编辑
     
    摘要:随着Windows10的发布,国内已经有越来越多的厂商上架了自家的通用应用程序客户端,比如QQ、微博、大麦等。所实话,他们设计的确实很好,很符合Windows10 的设计风格和产品理念,而对于开发者而言,当我们发现一个不错的UI设计风格不禁想自己动手也写一个类似的效果玩玩。前几天在微软的开发者社区中阅读全文
    posted @ 2015-08-27 19:56 hippieZhou 阅读 (1556) | 评论 (2) 编辑
     
    摘要:在当今,以云优先,移动优先技术为宗旨的时代下,大多数Apps都至少有一些与web服务或网络上其他设备的集成。这些包括应用程序,它获取天气在线内容,新闻或体育比赛的分数,媒体或下载的播客,甚至对等网络游戏、聊天或VoIP应用。这些应用程序通过使用各种各样的网络API来共同构建通用应用程序平台。 在wi阅读全文
    posted @ 2015-07-12 15:53 hippieZhou 阅读 (491) | 评论 (0) 编辑
     
    摘要:在做Win10开发的时候,我们可能经常会需要获得当前程序在在哪个平台设备上运行,用于UI和相关API的调用,那么可以通过什么方式知道当前APP运行的平台呢? 今天这里提供两个方法给大家做参考: 方法一:DeviceFamily 通过Windows.System.Profile.AnalyticsIn阅读全文
    posted @ 2015-07-09 17:36 hippieZhou 阅读 (314) | 评论 (0) 编辑
     
    摘要:最近由于项目需求,需要利用C#在UWP中与JS进行交互,由于还没有什么实战经验,所有就现在网上百度了一下,但是百度的结果显示大部分都是在Android和IOS上面的方法,UWP中的几乎没有。还好微软又他强大的MSDN社区,所有就在那里面找到了一个解题思路,于是就分享给大家。 1、首先,我们既然要与J阅读全文
    posted @ 2015-07-09 13:30 hippieZhou 阅读 (974) | 评论 (0) 编辑
     
    摘要:不多说,程序很简单,就是将集合中的数据进行排序,但使用到的知识点还是比较多的,大牛勿喷,谨献给初学者!直接上程序吧!阅读全文
    posted @ 2015-05-25 18:16 hippieZhou 阅读 (1707) | 评论 (0) 编辑
     
    摘要:这篇文章主要通过演示类在不同发展中的不通过定义方法,来向读者表述它们之间的区别和联系。 在C#1时代,我们喜欢这样定义类: 局限性:1、ArrayList没有提供与其内部内容有关的编译时信息,如果不慎写错,编译器也不会出现任何提示; 2、代码中为属性提供了公共的取值方法,这意味着如果添加对应的赋值方阅读全文
    posted @ 2015-05-25 14:40 hippieZhou 阅读 (1193) | 评论 (5) 编辑
     
    摘要:读操作: 方法1: 方法2: 方法3: 方法4: 方法5: 写操作: 方法1: 方法2: 方法3: 方法4: 方法5: 操作文件夹: CreateDirectory:创建文件夹 Delete:删除文件夹 Move:剪切文件夹 Exist:判断是否存在 GetFiles:获得指定的目录下所有文件的全路阅读全文
    posted @ 2015-05-07 23:13 hippieZhou 阅读 (393) | 评论 (0) 编辑
     
    摘要:out参数: 如果你在一个方法中,返回多个相同类型的值的时候,可以考虑返回一个数组。但是,如果返回多个不同类型的值的时候,返回数组就不行了,那么这个时候,我们可以考虑使用out参数。out参数就侧重于在一个方法中可以返回多个不同类型的值。 ref参数: 能够将一个变量带入一个方法中进行改变,改变完成阅读全文
    posted @ 2015-05-07 23:07 hippieZhou 阅读 (367) | 评论 (0) 编辑
     
    摘要:在实际开发中,我们不会直接使用拼写SQL语句的方法进行数据库操作,而是使用参数化的方法进行数据库操作,这样做的好处很多,不仅提高了程序的健壮性,同时也避免的SQL注入的问题。在这里,笔者为初学者提供一个SQLHelper模板,希望对新手有所启发。阅读全文
    posted @ 2015-05-07 21:48 hippieZhou 阅读 (497) | 评论 (0) 编辑
     
    摘要:熟悉数据绑定的朋友都知道,当我们在Model中获取一个对象的数据,常常需要对其进行数据转换后显示在UI界面上,比如你用bool类型存储了一个人的性别,但是在界面上却需要经过转化后显示为男或女; 今天又把数据绑定部分又看了一下,在这里就算是做个总结吧! 方法一:当我们定义一个类,该类中又有该类类型的一阅读全文
    posted @ 2015-04-27 19:57 hippieZhou 阅读 (1236) | 评论 (0) 编辑
     
  • 相关阅读:
    LINUX的LAMP环境搭配
    Jquery添加元素append及阻止表单提交submit
    django上传文件
    Html5本地存储LocalStorage
    Html5离线缓存详细讲解
    html5的canvas绘制迷宫地图
    EXT.NET复杂布局(二)——报表
    Silverlight——施工计划日报表(一)
    Silverlight——施工计划日报表(二)
    前端学HTTP之网络基础
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/12334505.html
Copyright © 2011-2022 走看看