• 实例快速上手 -ASP.NET 4.5新特性WebAPI从入门到精通


    在新出的MVC4中,增加了WebAPI,用于提供REST风格的WebService,新生成的WebAPI项目和典型的MVC项目一样,包含主要的ModelsViewsControllers等文件夹和Global.asax文件。Views对于WebAPI来说没有太大的用途,Models中的Model主要用于保存ServiceClient交互的对象,这些对象默认情况下会被转换为Json格式的数据迚行传输,Controllers中的Controller对应于WebService来说是一个Resource,用于提供服务。和普通的MVC一样,Global.asax用于配置路由规则。 
    环境准备 
         建议使用VS2012以上版本创建WebAPI,如果是使用VS2010,需要安装VS2010 SP1升级包,MVC4升级包,打开VS2012创建如下: 
    第一步:新建ASP.NET Web应用程序

    第二步:建议WebAPI

    新生成的WebAPI项目和典型的MVC项目一样,包含主要的ModelsViewsControllers等文件夹和Global.asax文件

    注意:再次强调Views对于WebAPI来说没有太大的用途,Models中的Model主要用于保存ServiceClient交互的对象,这些对象默认情况下会被转换为Json格式的数据进行传输,Controllers中的Controller对应于WebService来说是一个Resource,用于提供服务。和普通的MVC一样,Global.asax用于配置路由规则

    (二)Models 
    WCF中的数据契约形成鲜明对比的是,MVC WebAPI中的Model就是简单的POCO,没有任何别的东西,如,你可以创建如下的Model 

    public class UserModel 
    { 
            public int Id { get; set; } 
            public string UserName { get; set; } 
            public string PassWord { get; set; } 
    } 

    注意:Model必须提供public的属性,用于jsonxml反序列化时的赋值 
    (三)Controllers 
    MVC WebAPI中的Controllers和普通MVCControllers类似,不过不再继承于Controller,而改为继承APIApiController,一个Controller可以包含多个Action,这些Action响应请求的方法与Global中配置的路由规则有关,在后面结束Global时统一说明 
     
    (四)Global 
    默认情况下,模板自带了两个路由规则,分别对应于WebAPI和普通MVCWeb请求,默认的WebAPI路由规则如下 

                routes.MapHttpRoute( 
                     name: "DefaultApi", 
                     routeTemplate: "api/{controller}/{id}", 
                     defaults: new { id = RouteParameter.Optional } 
                 ); 

    可以看到,默认路由使用的固定的api作为Uri的先导,按照微软官方的说法,用于区分普通Web请求和WebService的请求路径: 
    可以看到,默认的路由规则只指向了Controller,没有指向具体的Action,因为默认情况下,对于Controller中的Action的匹配是和Action的方法名相关联的:具体来说,如果使用上面的路由规则,对应下面的Controller

    public class UserController : ApiController 
        { 
            public  List<UserModel> allModeList = new List<UserModel>() {  
              new UserModel(){ Id=1,UserName="zhang", PassWord="123"}, 
              new UserModel(){ Id=2,UserName="lishi", PassWord="123456"}, 
              new UserModel(){ Id=3,UserName="wang", PassWord="1234567"} 
            }; 
            //Get  api/User/ 
            public IEnumerable<UserModel> GetAll() 
            { 
                return allModeList; 
            } 
            //Get api/User/1 
            public IEnumerable<UserModel> GetOne(int id) { 
                return allModeList.FindAll((m) => { return m.Id == id; }); 
            } 
            //POST api/User/ 
            public bool PostNew(UserModel user) 
            { 
                try 
                { 
                    allModeList.Add(user); 
                    return true; 
                } 
                catch { 
                    return false; 
                } 
            } 
            //Delete api/User/ 
            public int DeleteAll() 
            { 
                return allModeList.RemoveAll((mode) => { return true; }); 
            } 
            //Delete api/User/1 
            public int DeleteOne(int id) { 
                return allModeList.RemoveAll((m) => { return m.Id == id; }); 
            } 
            //Put api/User  
            public int PutOne(int id, UserModel user) 
            { 
                List<UserModel> upDataList = allModeList.FindAll((mode) => { return mode.Id == id; }); 
                foreach (var mode in upDataList) 
                { 
                    mode.PassWord = user.PassWord; 
                    mode.UserName = user.UserName; 
                } 
                return upDataList.Count; 
            } 
    } 

    则,会有下面的对应关系:

    URLHttpMethod对应的Action
    /api/User GET GetALL
    /api/User/1 GET GetOne
    /api/User POST PostNew
    /api/User/1 DELETE DeleteOne
    /api/User DELETE DeleteALL
    /api/User PUT PutOne


      

    客户端JS调用

    function getAll() { 
            $.ajax({ 
                url: "api/User/", 
                type: 'GET', 
                success: function (data) { 
                    document.getElementById("modes").innerHTML = ""; 
                    $.each(data, function (key, val) { 
                        var str = val.UserName + ': ' + val.PassWord; 
                        $('<li/>', { html: str }).appendTo($('#modes')); 
                    }); 
                } 
            }).fail( 
            function (xhr, textStatus, err) { 
                alert('Error: ' + err); 
            }); 
       } 
    function find() { 
            $.ajax({ 
                url: "api/User/1" , 
                type: 'GET', 
                success: function (data) { 
                    document.getElementById("modes").innerHTML = ""; 
                    $.each(data, function (key, val) { 
                        var str = val.UserName + ': ' + val.PassWord; 
                        $('<li/>', { html: str }).appendTo($('#modes')); 
                    }); 
                } 
            }).fail( 
            function (xhr, textStatus, err) { 
                alert('Error: ' + err); 
            }); 
        } 
     
     
        function add() { 
     
            $.ajax({ 
                url: "api/User/", 
                type: "POST", 
                dataType: "json", 
                data: { "Id":4,"UserName": "admin", "PassWord": "666666"}, 
                success: function (data) { 
                    getAll(); 
                } 
            }).fail( 
            function (xhr, textStatus, err) { 
                alert('Error: ' + err); 
            }); 
     
        } 
     
        function removeUser() { 
            $.ajax({ 
                url: "api/User/3", 
                type: 'DELETE', 
                success: function (data) { 
                    document.getElementById("modes").innerHTML = ""; 
                    getAll(); 
                } 
            }).fail( 
            function (xhr, textStatus, err) { 
                alert('Error: ' + err); 
            }); 
        } 
     
        function removeAll() { 
            $.ajax({ 
                url: "api/User/", 
                type: 'DELETE', 
                success: function (data) { 
                    document.getElementById("modes").innerHTML = ""; 
                    getAll(); 
                } 
            }).fail( 
            function (xhr, textStatus, err) { 
                alert('Error: ' + err); 
            }); 
        } 
        function udpate() { 
            $.ajax({ 
                url: "api/User/1", 
                type: 'PUT', 
                dataType: "json", 
                data: { Id: 1, "UserName": "admin", "PassWord": "666666" }, 
                success: function (data) { 
                    document.getElementById("modes").innerHTML = ""; 
                    getAll(); 
                } 
            }).fail( 
            function (xhr, textStatus, err) { 
                alert('Error: ' + err); 
            }); 
        } 

    这样就实现了最基本的CRUD操作。 
    扩展需求 
    问题1:我想按照用户名称(UserName)进行查询,怎么办? 
    办法:第一步:在UserController类中加一个方法名称叫:GetUserByName,如下所示: 

    public UserModel GetUserByName(string userName) { 
                return allModeList.Find((m) => { return m.UserName.Equals(userName); }); 
    } 

    第二步:在客户端index.cshtml中调用 

    function getUserByName() { 
            $.ajax({ 
                url: "api/User/zhang", 
                type: 'GET', 
                success: function (data) { 
                    document.getElementById("modes").innerHTML = ""; 
                    var str = data.UserName + ': ' + data.PassWord; 
                    $('<li/>', { html: str }).appendTo($('#modes')); 
                } 
            }).fail( 
            function (xhr, textStatus, err) { 
                alert('Error: ' + err); 
            }); 
       } 

    如果URL是: url: "api/User/zhang",将会报错:Bad Request 
    原因是他会自动调用我们的GetOne(int id) 这个方法,类型转换出错 
    解决办法: 
    改变URL为: url: "api/User/?userName=zhang"
    问题2:我想按用户名称(UserName) 和用户密码(PassWord)一起来进行查询,怎么办? 
    解决办法 
    第一步:UserController类中,可以重载一个GetUserByName的方法,如下所示: 

    public UserModel GetUserByName(string userName) { 
                return allModeList.Find((m) => { return m.UserName.Equals(userName); }); 
            } 

    第二步:客户端调用:

    function getUserByName() { 
            $.ajax({ 
                url: "api/User/?userName=zhang&passWord=123", //这里尤其需要注意 
                type: 'GET', 
                success: function (data) { 
                    document.getElementById("modes").innerHTML = ""; 
                    var str = data.UserName + ': ' + data.PassWord; 
                    $('<li/>', { html: str }).appendTo($('#modes')); 
                } 
            }).fail( 
            function (xhr, textStatus, err) { 
                alert('Error: ' + err); 
            }); 
       } 

    路由规则扩展 
    和普通的MVC一样,MVC WebAPI支持自定义的路由规则,如:在上面的操作中,路由规则使用 
    "api/{controller}/{id}" 
    则限定了使用GET方式利用URL来传值时,controller后面的接收参数名为id,但是在Controller中,如果GetOne方法的接收参数名为key,是不会被匹配的,这是只需要新增一个新的路由规则,或修改原先的路由规则为: 
    "api/{controller}/{key}",如下所示:

    config.Routes.MapHttpRoute( 
                    name: "DefaultApi", 
                    routeTemplate: "api/{controller}/{key}", 
                    defaults: new { key = RouteParameter.Optional } 
                ); 

    当然,可以对路由进行更深的扩展,如:扩展成和普通MVC一样的路由: 
    "api/{controller}/{action}/{id}" 
    这样,就要求同时使用ActionHTTP方法进行匹配当然,根据微软的说法,这种使用是不被推荐的,因为这不符合大家对WebService的一般认知: 
    使用Attribute声明HTTP方法

           [HttpGet] 
           public IEnumerable<TestUseMode> FindAll() 
           [HttpGet] 
           public IEnumerable<TestUseMode> FindByKey(string key) 
           [HttpPost] 
           public bool Add(TestUseMode mode) 
           [HttpDelete] 
           public int RemoveByKey(string key) 
           [HttpDelete] 
           public int RemoveAll() 
           [HttpPut] 
           public int UpdateByKey(string key, string value) 
           [NonAction]   
           public string GetPrivateData() 

    当然,我只列出了方法名,而不是这些方法真的没有方法体...方法体是不变的,NoAction表示这个方法是不接收请求的,即使以GET开头。如果感觉常规的GETPOSTDELETEPUT不够用,还可以使用AcceptVerbs的方式来声明HTTP方法,如:

    [AcceptVerbs("MKCOL", "HEAD")] 
    public int UpdateByKey(string key, string value) 
    { 
                List<TestUseMode> upDataList = allModeList.FindAll((mode) => { if (mode.ModeKey == key) return true; return false; }); 
                foreach(var mode in upDataList) 
                { 
                    mode.ModeValue = value; 
                } 
                return upDataList.Count; 
    } 

    附:什么是REST风格? 参考:什么是REST风格 
    http://hi.baidu.com/yankaiwei/item/1f0b37dd922d53ef3cc2cb69 
    第二部分:综合示例:应用ASP.NET MVC4+WebAPI+FluentData开发Web应用 
    第一步:创建数据库 
    NorthWind数据库的Customers

    Create DataBase NorthWind 
    Go 
    Use NorthWind 
    Go 
    CREATE TABLE [dbo].[Customers]( 
        [CustomerID] [nchar](5) NOT NULL, 
        [CompanyName] [nvarchar](40) NOT NULL, 
        [ContactName] [nvarchar](30) NULL, 
        [ContactTitle] [nvarchar](30) NULL, 
        [Address] [nvarchar](60) NULL, 
        [City] [nvarchar](15) NULL, 
        [Region] [nvarchar](15) NULL, 
        [PostalCode] [nvarchar](10) NULL, 
        [Country] [nvarchar](15) NULL, 
        [Phone] [nvarchar](24) NULL, 
        [Fax] [nvarchar](24) NULL, 
    CONSTRAINT [PK_Customers] PRIMARY KEY CLUSTERED  
    ( 
        [CustomerID] ASC 
    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
    ) ON [PRIMARY] 
     
    GO 

    第二步:创建 FluentData.Entity层,创建Customer实体类 

    namespace FluentData.Entity 
    { 
        public class Customer 
        { 
            public string CustomerID { get; set; } 
            public string CompanyName { get; set; } 
            public string ContactName { get; set; } 
            public string ContactTitle { get; set; } 
            public string Address { get; set; } 
            public string City { get; set; } 
            public string Region { get; set; } 
            public string PostalCode { get; set; } 
            public string Country { get; set; } 
            public string Phone { get; set; } 
            public string Fax { get; set; } 
        } 
    } 

    第三步:利用FluentData做数据的持久化 
    首先引入FluentData.cs (见附件) 
    其次:创建DBHelper类,代码如下:

    public class DBHelper 
        { 
            public static IDbContext Context() { 
                //return new DbContext().ConnectionString("server=127.0.0.1;uid=sa;pwd=sa;database=TestDB", new SqlServerProvider()); 
                return new DbContext().ConnectionStringName("connString", new SqlServerProvider()); 
            } 
        } 

    然后不要忘记修改ASP.NET MVC层所在的Web.config,加入数据库连结字符串:

      <connectionStrings> 
        <add name="connString" connectionString="server=127.0.0.1;database=Northwind;uid=sa;pwd=sa;"/> 
      </connectionStrings>

    第三步:创建 CustomerService数据持久化类,代码如下:

    public class CustomerService 
        { 
            private IDbContext context = DBHelper.Context(); 
            public Customer Select(string customerId){ 
     
                return context.Select<Customer>("*").From("Customers").Where("CustomerID=@0").Parameters(customerId) 
                       .QuerySingle(); 
                 
            } 
            public List<Customer> SelectAll() { 
                return context.Select<Customer>("*").From("Customers").QueryMany(); 
            } 
     
            public List<Customer> SelectAll(string sortExpression) { 
                if (String.IsNullOrEmpty(sortExpression)) return null; 
                return context.Select<Customer>("*").From("Customers").OrderBy(sortExpression).QueryMany(); 
            } 
     
            public List<Customer> SelectAll(int currentPageIndex,int maxRows, string sortExpression) 
            { 
                var select = context.Select<Customer>("*").From("Customers"); 
                if (maxRows > 0) { 
                    if (currentPageIndex == 0) currentPageIndex = 1; 
                    select.Paging(currentPageIndex, maxRows); 
                } 
                if (!string.IsNullOrEmpty(sortExpression)) { 
                    select.OrderBy(sortExpression); 
                } 
     
                return select.QueryMany(); 
            } 
     
            public int CountAll() { 
                return context.Sql("select count(*) from Customers").QuerySingle<int>(); 
            } 
     
            public int Insert(Customer customer) { 
                return context.Insert<Customer>("Customers", customer).Execute(); 
            } 
     
            public int Update(Customer customer) { 
                return context.Update<Customer>("Customers", customer).Where("CustomerID", customer.CustomerID).Execute(); 
            } 
     
            public int Delete(string customerId) { 
                return context.Delete("Customers").Where("CustomerID", customerId).Execute(); 
            } 
     
            public int Delete(Customer customer) 
            { 
                return this.Delete(customer.CustomerID); 
            } 
        } 

    第四步:Web API,创建CustomerController 
    注意要引用:FluentData.EntityFluentData.DAL 程序集

    public class CustomerController : ApiController 
        { 
            private CustomerService customerService = new CustomerService(); 
            //Select All 
            public IEnumerable<Customer> Get() 
            { 
                return customerService.SelectAll(); 
            } 
     
            //Select By Id 
            public Customer Get(string id) 
            { 
                return customerService.Select(id); 
            } 
     
            //Insert 
            public void Post(Customer customer) 
            { 
                customerService.Insert(customer); 
            } 
     
     
            //Update 
            public void Put(string id, Customer obj) 
            { 
                customerService.Update(obj); 
            } 
     
            //Delete 
            public void Delete(string id) 
            { 
                customerService.Delete(id); 
            } 
        } 

    第五步:View层代码

    namespace MyWebApI.Controllers 
    { 
        public class HomeController : Controller 
        { 
            public ActionResult Index() 
            { 
                return View(); 
            } 
     
            public ActionResult Test() { 
                return View(); 
            } 
     
            public ActionResult CustomerManager() { 
                return View(); 
            } 
        } 
    } 

    然后创建View 

        <table id="customerTable" border="1" cellpadding="3"  style="700px"> 
            <tr> 
                <th>Customer ID</th> 
                <th>Company Name</th> 
                <th>Contact Name</th> 
                <th>Country</th> 
                <th>Actions</th> 
            </tr> 
            <tr> 
                <td><input type="text" id="txtCustomerId"  style="100px" size="5"/></td> 
                <td><input type="text" id="txtCompanyName"  style="150px" /></td> 
                <td><input type="text" id="txtContactName"  style="150px"/></td> 
                <td><input type="text" id="txtCountry"  style="150px"/></td> 
                <td><input type="button" name="btnInsert" value="Insert"  style="150px"/></td> 
            </tr> 
        </table> 
     
    <script type="text/javascript"> 
        $(function () { 
            $.getJSON("api/Customer", LoadCustomers); 
        }); 
     
        function LoadCustomers(data) { 
            $("#customerTable").find("tr:gt(1)").remove(); 
            $.each(data, function (key, val) { 
                var tableRow = '<tr>' + 
                                '<td>' + val.CustomerID + '</td>' + 
                                '<td><input type="text" value="' + val.CompanyName + '" /></td>' + 
                                '<td><input type="text" value="' + val.ContactName + '" /></td>' + 
                                '<td><input type="text" value="' + val.Country + '" /></td>' + 
                                '<td><input type="button" name="btnUpdate" value="修改" /> <input type="button" name="btnDelete" value="删除" /></td>' + 
                                '</tr>'; 
                $('#customerTable').append(tableRow); 
            }); 
     
            $("input[name='btnInsert']").click(OnInsert); 
            $("input[name='btnUpdate']").click(OnUpdate); 
            $("input[name='btnDelete']").click(OnDelete); 
        } 
     
        function OnInsert(evt) { 
            var customerId = $("#txtCustomerId").val(); 
            var companyName = $("#txtCompanyName").val(); 
            var contactName = $("#txtContactName").val(); 
            var country = $("#txtCountry").val(); 
            var data = '{"CustomerID":"' + customerId + '","CompanyName":"' + companyName + '","ContactName":"' + contactName + '","Country":"' + country + '"}'; 
     
            $.ajax({ 
                type: 'POST', 
                url: '/api/Customer/', 
                data: data, 
                contentType: "application/json; charset=utf-8", 
                dataType: 'json', 
                success: function (results) { 
                    $("#txtCustomerId").val(''); 
                    $("#txtCompanyName").val(''); 
                    $("#txtContactName").val(''); 
                    $("#txtCountry").val(''); 
                    $.getJSON("api/customers" + new Date().getTime(), LoadCustomers); 
                    alert('添加成功!'); 
                } 
            }).fail( 
            function (xhr, textStatus, err) { 
                alert('添加失败,原因如下: ' + err); 
            }); 
        } 
        function OnUpdate(evt) { 
     
            var input; 
            var customerId = $(this).parent().parent().children().get(0).innerHTML; 
     
            input = $($(this).parent().parent().children().get(1)).find("input"); 
            //input.removeAttr("disabled"); 
            var companyName = input.val(); 
     
            input = $($(this).parent().parent().children().get(2)).find("input"); 
            //input.removeAttr("disabled"); 
            var contactName = input.val(); 
     
            input = $($(this).parent().parent().children().get(3)).find("input"); 
            //input.removeAttr("disabled"); 
            var country = input.val(); 
     
            var data = '{"CustomerID":"' + customerId + '","CompanyName":"' + companyName + '","ContactName":"' + contactName + '","Country":"' + country + '"}'; 
             
            $.ajax({ 
                type: 'PUT', 
                url: '/api/Customer/' + customerId, 
                data: data, 
                contentType: "application/json; charset=utf-8", 
                dataType: 'json', 
                success: function (results) { 
                    $.getJSON("api/Customer" + new Date().getTime(), LoadCustomers); 
                    alert('修改成功 !'); 
                } 
            }).fail( 
            function (xhr, textStatus, err) { 
                alert('修改失败,原因如下: ' + err); 
            }); 
        } 
        function OnDelete(evt) { 
            var customerId = $(this).parent().parent().children().get(0).innerHTML; 
            //var data = '{"id":"' + customerId + '"}'; 
            //var row = $(this).parent().parent(); 
     
            $.ajax({ 
                type: 'DELETE', 
                url: '/api/Customer/' + customerId, 
                contentType: "application/json; charset=utf-8", 
                dataType: 'json', 
                success: function (results) { 
                    $.getJSON("api/Customer?"+new Date().getTime(), LoadCustomers); 
                    alert('成功删除!'); 
                } 
            }).fail( 
            function (xhr, textStatus, err) { 
                alert('删除失败,原因如下: ' + err); 
            }); 
     
        } 
    </script> 

    第三部分:Web API高级部分 
    在第一部分和大家一起学习了建立基本的WebAPI应用,第二部分写了一个综合示例,立刻就有人想到了一些问题:1.客户端和WebService/WebAPI之间文件传输2.客户端或者服务端的安全控制要解决这些问题,要了解一下WebAPI的基本工作方式。 
    (一)WebAPI中工作的Class 
    MVC中大家都知道,获取RequestResponse使用HttpRequestHttpResponse两个类,在WebAPI中使用两外两个类:HttpRequestMessageHttpResponseMessage,分别用于封装RequsetResponse。除了这两个类之外,还有一个常见的抽象 类:HttpMessageHandler,用于过滤和加工HttpRequestMessageHttpResponseMessage 
     
    (二)解决第一个问题:客户端和WebService之间文件传输其 实第一个问题之所以被提出来应该是和客户端有关,如果客户端的请求是我们手写提交的,比如使用HttpClient封装的请求,则要传递文件之前,我们一 般会进行一次序列化,转化为二进制数组之类的,在网络上传输。这样的话,在Controller中的Action参数里,我们只需要接收这个二进制数组类 型的对象就可以了。但是如果客户端是Web Form呢,比如我们提交一个Form到指定的ControllerAction中,这个Action要接收什么类型的参数呢?或者我们问另外一个问题,如果我将Web Form提交到一个WebAPIAction中 ,我要怎么去取出这个表单中的数据呢?其 实我们应该想到:我们的Action设置的参数之所以能够被赋值,是因为WebAPI的架构中在调用Action时将HTTP请求中的数据解析出来分别赋 值给Action中的参数,如果真是这样的话,我们只需要在Action中获取到HTTP请求,然后直接获取请求里面的数据,就能解决上面的问题。这 种想法是正确的,只不过,此时的HTTP请求已经不是最原始的HTTP Request,而是已经被转化成了HttpRequestMessage,在Action中,我们可以直接调用base.Requet来得到这个 HttpRequestMessage实例,通过这个实例我们就可以随心所欲的取出HTTP请求中想要的数据 
     
    2.1RequestMessage中获取普通表单数据 
    这里的普通表单是指不包含File的表单,也就是说表单的enctype值不是multipart/form-data,这时,表单的数据默认情况下是以Json来传递的如下页面

    <form name="form" action="~/api/File/Register" method="post"> 
        <input type="text" name="userName" /> 
        <br /> 
        <input type="text" name="passWord"  /> 
        <br />     
        <input type="submit" value="Submit" />  
    </form>

    ApiContoller:

      public class FileController : ApiController 
      { 
            [HttpPost] 
            public async Task  Register() 
            { 
                  HttpContent content = Request.Content; 
                 var jsonValue = await content.ReadAsStringAsync(); 
                Console.WriteLine(jsonValue); 
            } 
      }

    执行:userName字段输入admin,passWord字段输入123456 
    传递到服务器端输出的是:userName=admin&passWord=12345 
    2.2RequestMessage中获取multipart表单数据将view页面改写为:

    <form name="form" action="~/api/File/SubmitFile" method="post" enctype="multipart/form-data" > 
        <input type="text" name="userName2"  /> 
        <br /> 
        <input type="text" name="passWord2" /> 
        <br /> 
        <input type="file" name="file" id="upFile" /> 
        <br /> 
        <input type="submit" value="Submit" /> 
    </form>

    ApiController代码:

            public async Task<string> SubmitFile() 
            { 
                // 检查是否是 multipart/form-data 
                if (!Request.Content.IsMimeMultipartContent("form-data")) 
                    throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); 
                var path = HttpContext.Current.Server.MapPath("~/File"); 
                // 设置上传目录 
                var provider = new MultipartFormDataStreamProvider(path); 
                // 接收数据,并保存文件 
                var bodyparts = await Request.Content.ReadAsMultipartAsync(provider); 
                var file = provider.FileData[0];//provider.FormData  
                string orfilename = file.Headers.ContentDisposition.FileName.TrimStart('"').TrimEnd('"'); 
                FileInfo fileinfo = new FileInfo(file.LocalFileName);   
                String ymd = DateTime.Now.ToString("yyyyMMdd", System.Globalization.DateTimeFormatInfo.InvariantInfo); 
                String newFileName = DateTime.Now.ToString("yyyyMMddHHmmss_ffff", System.Globalization.DateTimeFormatInfo.InvariantInfo); 
                string fileExt = orfilename.Substring(orfilename.LastIndexOf('.')); 
                fileinfo.CopyTo(Path.Combine(path, newFileName + fileExt), true); 
                fileinfo.Delete();  
                string result = ""; 
                // 获取表单数据 
                result += "formData userName: " + bodyparts.FormData["userName1"]; 
                result += "<br />"; 
                // 获取文件数据 
                result += "fileData headers: " + bodyparts.FileData[0].Headers; // 上传文件相关的头信息 
                result += "<br />"; 
                result += "fileData localFileName: " + bodyparts.FileData[0].LocalFileName; // 文件在服务端的保存地址,需要的话自行 rename 或 move 
                return result; 
            } 

    还有一种简单的方式

    public string Post()  
             { 
                    HttpPostedFile file = HttpContext.Current.Request.Files[0]; 
                    string strPath = "D:\MyProjects\StudySolution\RestDemo\Upload\test2.rar" ; 
                    file.SaveAs(strPath); 
                    string result = "0"; 
                    return result; 
              } 

    注:上述的文件上传代码涉及到asyncTaskawait 这些关键字是 asp.net mvc 中异步编程的知识点,在这里暂不做过多解释,不了解的同学可以去先了解一下这块的内容,后期在我的系列主题文章中也还会有这块知识点的讲解,敬请关注! 
    解决第二个问题:客户端或者服务端的安全控制 
    WebAPI的工作方式:HTTP的请求最先是被传递到HOST中的,如果WebAPI是被寄宿在IIS上的,这个HOST就是IIS上,HOST是没有能力也没有必要进行请求的处理的,请求通过HOST被转发给了HttPServer此时已经进入WebAPI的处理加工范围,HttpServerSystem.Net.HTTP中的一个类,通过HttpServer,请求被封装成了WebAPI中的请求承载 类:HttpRequestMessage,这个封装后的请求可以经过一系列自定义的Handler来处理,这些handler串联成一个 pipeline,最后请求会被传递给HttpControlDispather,这个类通过对路由表的检索来确定请求将被转发到的具体的 Controller中的Action。 
       由此我们早就可以看出,想要解决第二个问题,可以直接在Handler PipeLine中进行,这种AOP风格的过滤器(拦截器)在RESTWebservice的安全验证中应用很广,一般大家比较乐于在HTTP头或者在 HTTP请求的URL中加上身份验证字段进行身份验证,下面举一个在Http头中添加身份验证信息的小例子: 
    3.1客户端客户端的customhandler用于将身份验证信息添加入报头

    class RequestCheckHandler : DelegatingHandler 
    { 
            protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) 
            { 
                request.Headers.Add("keyword", "ibeifeng"); 
                return base.SendAsync(request, cancellationToken); 
            } 
    } 

    注:1.customhandler继承自DelegatingHandler类,上面已经说过,WebAPI的客户端和服务端被设计为相互对应的两套结构,所以不论是在客户端还是服务端,customhandler都是继承自DelegatingHandler2.DelegatingHandlersendAsync方法便是处理请求和接受请求时会被调用的方法,该方法返回值是HttPResponseMessage,接收的值为HttpRequestMessage,符合我们的一般认知3.方法的最后,调用base.SendAsync是将Request继续向该pipeline的其他customHandler传递,并获取其返回值。由于该方法不包含Response的处理逻辑,只需直接将上一个CustomHandler的返回值直接返回 
     
    客户端主程序: 

    static void Main(string[] args) 
    { 
               HttpClient client = new HttpClient(new RequestCheckHandler() { InnerHandler = new HttpClientHandler() }); 
                HttpResponseMessage response = client.GetAsync("http://localhost:47673/api/File/GetUserInfo?userName=admin").Result; 
                response.Content.ReadAsStringAsync().ContinueWith((str) => { Console.WriteLine(str.Result); }); 
    } 

    客户端的主程序创建了一个HttpClientHttpClient可以接受一个参数,该参数就是CustomHandler,此处我们嵌入了我们定义的 RequestUpHandler,用于对Request报头进行嵌入身份验证码的处理,CustomHandler通过InnerHandler属性嵌 入其内置的下一个CustomHandler,此处,由于没有下一个CustomerHandler,我们直接嵌入HttpClientHandler用 于将HttpRequestMessage转化为HTTP 请求、将HTTP响应转化为HttpResponseMessage 
     
    3.2服务端服务端的customHandler用于解析HTTP报头中的身份认证码 

    public class SecurityHandler : DelegatingHandler 
    { 
       protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken  
          cancellationToken) 
        { 
                int matchHeaderCount = request.Headers.Count((item) => 
                { 
                    if ("keyword".Equals(item.Key)) 
                    { 
                        foreach (var str in item.Value) 
                        { 
                            if ("ibeifeng".Equals(str)) 
                            { 
                                return true; 
                            } 
                        } 
                    } 
                    return false; 
                }); 
                if (matchHeaderCount>0) 
                { 
                    return base.SendAsync(request, cancellationToken); 
                } 
                return Task.Factory.StartNew<HttpResponseMessage>(() => { return new HttpResponseMessage(HttpStatusCode.Forbidden); }); 
       } 
    } 

    另: 
    FileController中加一个测试方法:

            [HttpGet] 
            public string GetUserInfo(string userName) 
            { 
                if (userName == "admin") 
                { 
                    return "success"; 
                } 
                else { 
                    return "failed"; 
                }  
            }

    注:代码的处理逻辑很简单:如果身份验证码匹配成功,则通过base.SendAsync继续将请求向下传递,否则返回直接中断请求的传递,直接返回一个响应码为403的响应,指示没有权限。注意由于SendAsync的返回值需要封装在Task之中,所以需要使用Task.Factory.StartNew将返回值包含在TaskcustomHandler注入到HOST中本例中WebAPI HOSTIIS上,所以我们只需将我们定义的CustomHandlerApplication_Start中定义即可 

    protected void Application_Start() 
            { 
                //省略其他逻辑代码 
                GlobalConfiguration.Configuration.MessageHandlers.Add(new SecurityHandler ()); 
            } 

    由于WebAPI HostIIS上,所以HttpServerHttpControllerDispatcher不用我们手工处理 
    在加上上面的处理后,如果没有身份验证码的请求,会得到如下的响应 
     
     
    总结 
    1.使用WebAPI的目的 
      当你遇到以下这些情况的时候,就可以考虑使用Web API了。 
      a. 需要Web Service但是不需要SOAP 
      b. 需要在已有的WCF服务基础上建立non-soap-based http服务 
      c. 只想发布一些简单的Http服务,不想使用相对复杂的WCF配置 
      d. 发布的服务可能会被带宽受限的设备访问 
      e. 希望使用开源框架,关键时候可以自己调试或者自定义一下框架 
    2.使用WebAPI的几种方式 与注意事项 
    3.使用WebAPI实现文件上传 
    4.如何加强WebAPI的安全性 

  • 相关阅读:
    python--模块与包
    内置函数 的总结
    迭代器 生成器 列表推导式 生成器表达式的一些总结
    函数的有用信息 带参数的装饰器 多个装饰器装饰一个函数
    函数名的应用(第一对象) 闭包 装饰器
    动态参数 名称空间 作用域 作用域链 加载顺序 函数的嵌套 global nonlocal 等的用法总结
    函数的初识 函数的返回值 参数
    文件操作 常用操作方法 文件的修改
    遍历字典的集中方法 集合的作用 以及增删查的方法
    计算机硬件的小知识
  • 原文地址:https://www.cnblogs.com/guyun/p/4581297.html
Copyright © 2020-2023  润新知