ASP.NET Core开发常见问题
ASP.NET Core中返回 json 数据首字母大小写问题
在asp.net core中使用ajax请求动态绑定数据时遇到问题:
后台返回数据字段首字母为定义的大写,返回的数据没有问题;但是在前台得到的数据,字段首字母却变成了小写
此时用定义的首字母大写字段去接收数据会显示undefined,这是因为在asp.net core中json序列化默认使用驼峰格式处理字段,首字母变成小写,所以获取不到数据。
解决方法:在Startup类的ConfigureServices()方法中进行配置,DefaultContractResolver() 原样输出,返回的 json 与后台定义一致
public void ConfigureServices(IServiceCollection services) { ... services.AddMvc().AddJsonOptions(opt => { opt.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver();//json字符串大小写原样输出 }); }
此时json字符串字段首字母大小与后台定义一致
参考:ASP.NET Core中返回 json 数据首字母大小写问题
WebAPI——修改默认监听端口
添加host.json文件
{ "urls": "https://localhost:44389;http://localhost:44380" }
在Program.cs中修改CreateWebHostBuilder方法
public static IWebHostBuilder CreateWebHostBuilder(string[] args) { var configuration = new ConfigurationBuilder().SetBasePath(Environment.CurrentDirectory) .AddJsonFile("host.json") .Build(); var url = configuration["urls"]; return WebHost.CreateDefaultBuilder(args) .UseUrls(url) .UseStartup<Startup>(); }
参考官网:ASP.NET Core Web 主机
HttpClient Post json参数请求
被请求的接口代码如下:
//被请求接口 // POST api/values [HttpPost] public MyProperty Post([FromBody] MyProperty value) { return value; } //参数 public class MyProperty { public int ID { get; set; } public string Name { get; set; } }
注:若提交报错:通过post提交Json数据时,接口返回,"{\"\":[\"Unexpected character encountered while parsing value: {. Path '', line 1, position 1.\"]}"
则可能是因为参数 写成了string类型([FromBody] string value)
使用HttpClient的PostAsync方法发送Json数据请求
public class JsonContent : StringContent { public JsonContent(object obj) : base(JsonConvert.SerializeObject(obj), Encoding.UTF8, "application/json") { } } public static string HttpClientPost(string url, object datajson) { HttpClient httpClient = new HttpClient();//http对象 //表头参数 httpClient.DefaultRequestHeaders.Accept.Clear(); httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); //转为链接需要的格式 HttpContent httpContent = new JsonContent(datajson); //请求 HttpResponseMessage response = httpClient.PostAsync(url, httpContent).Result; if (response.IsSuccessStatusCode) { Task<string> t = response.Content.ReadAsStringAsync(); if (t != null) { return t.Result; } } return ""; }
调用
//调用 MyProperty myProperty = new MyProperty(); myProperty.ID = 666; myProperty.Name = "Tom猫"; var result = HttpClientPost("http://localhost:59958/api/values", myProperty); Console.WriteLine(result);
wwwroot目录下文件访问不了
private IHostingEnvironment _host; public NewsController(IHostingEnvironment host) { this._host = host; } string webRootPath = _host.WebRootPath; //获取的即是wwwroot
net core 3.1中 推荐用IHostEnvironment,路径获取用:Path.Combine(_host.ContentRootPath, "wwwroot")
方案:允许站点不识别content-type下载文件(即:不受mime类型限制下载)
对于netcore的web项目而言,内置了一些content-type允许下载的文件类型,eg,常见的image、pdf、zip等,有些却未运行,eg:nupkg、apk,
要想.apk,.nupkg.cs等后缀的文件不被限制,我们可以:
app.UseStaticFiles(new StaticFileOptions { //设置不限制content-type ServeUnknownFileTypes = true });
上面例子我们能够使用 ServeUnknownFileTypes = true; 直接设置无限制下载文件类型,这种通常不是太好或者说不允许,亦或者不常说的不安全吧;如果我们只需要增加.nupkg和.apk后缀的文件的下载,那么可以通过如下代码来添加mime类型,如:
app.UseStaticFiles(new StaticFileOptions { //ServeUnknownFileTypes = true ContentTypeProvider = new FileExtensionContentTypeProvider(new Dictionary<string, string> { { ".apk","application/vnd.android.package-archive"}, { ".nupkg","application/zip"} }) });
再比如要下载一个byte[]写入的 xx.tsa二进制文件,可以加一个:{ ".tsa", "application/octet-stream" }
参考:
StaticFiles 之 FileExtensionContentTypeProvider
文件下载
1、后端直接返回 文件的地址,前端定位,直接下载
后端:
ViewBag.zipFile = zipFile ; // zipFile文件在服务器上的绝对路径
html:
<el-button type="primary" @@click="onDownTSA('@ViewBag.zipFile')">下载文件(.zip)</el-button>
onDownTSA(zipFile) {
window.open(zipFile, "_blank")
}
2、后端返回文件对象,前端请求下载
- 下载完不需要返回值
onDownTSA(zipName) { downloadFiles("/ApplyTimeStamp/DownTsaFile", { zipName: zipName }); } //无返回值 function downloadFiles(url, params) { try { var form = document.createElement("form"); form.id = "downloadForm"; form.style.display = "none"; form.target = ""; form.method = "post"; form.action = url; for (var key in params) { var input = document.createElement("input"); input.type = "hidden"; input.name = key; input.value = params[key]; form.appendChild(input); } document.body.appendChild(form); form.submit(); document.body.removeChild(form); } catch (ex) { Vue.prototype.$message.error(ex); } }
后端:
[Description("下载证明文件")] public async Task<IActionResult> DownTsaFile(string zipName) { try { byte[] bytes = null; zipName = zipName.Trim(); string path = Path.Combine("wwwroot", WebConstant.TsaFileDirName, zipName); if (System.IO.File.Exists(path)) { bytes = await System.IO.File.ReadAllBytesAsync(path); } return File(bytes, "application/octet-stream", zipName); } catch (Exception ex) { LogHelper.Error(ex.Message, ex); ObjectResult objectResult = new ObjectResult(new ResultDTO()); return objectResult; //没什么用处 } }
- 下载完后需要返回值
使用 Blob 构造函数可以直接在客户端上创建和操作 Blob(通常等效于一个文件)。
Internet Explorer 10 的 msSaveBlob 和 msSaveOrOpenBlob 方法允许用户在客户端上保存文件,方法如同从 Internet 下载文件,这是此类文件保存到“下载”文件夹的原因。
onDownTSA(zipName) { downloadFiles("/ApplyTimeStamp/DownTsaFile", { zipName: zipName }); } //有返回值 function downloadFiles(url, params) { try { var form = document.createElement("form"); form.id = "downloadForm"; form.style.display = "none"; form.target = ""; form.method = "post"; let filename = ""; for (var key in params) { var input = document.createElement("input"); input.type = "hidden"; input.name = key; input.value = params[key]; filename = input.value; form.appendChild(input); } document.body.appendChild(form); let dataN = new FormData(form); axios.post(url, dataN, { responseType: 'blob' }).then(response => { var data = response.data; if (response.status == 200) { //请求成功 if (window.navigator.msSaveOrOpenBlob) { let blob = new Blob([data]) navigator.msSaveBlob(blob, filename); } else { let blob = new Blob([data]) var link = document.createElement('a'); link.href = window.URL.createObjectURL(blob); link.download = filename; link.click(); window.URL.revokeObjectURL(link.href); } Vue.prototype.$message({ message: '下载成功', type: 'success' }); document.body.removeChild(form); url = "/ApplyTimeStamp/Index"; location.href = url; } }).catch((ex) => { //请求失败 if (ex.response.status == 404) { Vue.prototype.$message.error('文件不存在或者已被下载!'); } else if (ex.response.status == 400) { Vue.prototype.$message.error('参数异常:文件名为空异常!'); } }); } catch (ex) { Vue.prototype.$message.error(ex); } }
后端:
[Description("下载证明文件")] public async Task<IActionResult> DownTsaFile(string zipName) { var error = new ResultDTO(0, ""); try { if (!string.IsNullOrEmpty(zipName)) { byte[] bytes = null; zipName = zipName.Trim(); string path = Path.Combine("wwwroot", WebConstant.TsaFileDirName, zipName); if (System.IO.File.Exists(path)) { bytes = await System.IO.File.ReadAllBytesAsync(path); } else { throw new Exception($"文件不存在或者已被下载"); } if (System.IO.File.Exists(path)) { System.IO.File.Delete(path); } Response.StatusCode = (int)HttpStatusCode.OK; //HttpUtility.UrlEncode(zipName,Encoding.UTF8) return File(bytes, "application/octet-stream", zipName); } else { error.Code = -1; error.Msg = $"参数异常:文件名为空异常"; ObjectResult objectResult = new ObjectResult(error); Response.StatusCode = (int)HttpStatusCode.BadRequest; return objectResult; } } catch (Exception ex) { LogHelper.Error(ex.Message, ex); error.Code = -1; error.Msg = $"下载文件异常:{ex.Message}"; ObjectResult objectResult = new ObjectResult(error); Response.StatusCode = (int)HttpStatusCode.NotFound; return objectResult; //没什么用处 } }
参考:
jQuery 利用其 AJAX 下载后端返回的 application/octet-stream 二进制数据
Vue responseType中blob和arrayBuffer下载文件以及JSON处理