【ASP.Net MVC】AspNet Mvc一些总结

时间:2023-03-08 22:49:00
【ASP.Net MVC】AspNet Mvc一些总结

AspNet Mvc一些总结

RestaurantReview.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Mvc;
using Antlr.Runtime; namespace Kwin.AspNetMvc.OdeToFood.Models
{
/// <summary>
///
/// </summary>
/// <remarks>
/// 1.Bind特性用于锁定可以进行模型绑定的属性,
/// [Bind(Include = "City, Country, Rating")] ,白名单的形式,推荐
/// [Bind(Exclude = "Id, Name")] ,黑名单的形式
///
/// 可以在三个位置设置:
/// 1.Global.asax.cs 类型列表中设置绑定规则
/// 2.在类定义上<see cref="RestaurantReview"/>
/// 3.在方法参数上
/// --------------------
/// 2.数据验证方法:
/// http://developer.51cto.com/art/201404/435126.htm
/// http://www.cnblogs.com/zhangkai2237/archive/2012/12/12/2814825.html
///
/// 1).数据验证,方法:实现IDataErrorInfo,演示:<see cref="RestaurantReview"/>
/// 成员函数在UpadtaModel时会被调用,(永远不要相信客户端,服务器这边再检查一遍)
/// --------------------
/// 2).数据验证,方法:继承ValidationAttribute
/// 演示MaxWordsAttribute<see cref="MaxWordsAttribute"/>
/// 这个特性和普通数据注解的区别是普通数据注解仅仅只能验证Model的一个属性,
/// 需要注意的是,自定义的数据注解不支持客户端验证,所有的数据需要提交之后再服务端验证,所以如果要同时实现客户端验证需要自己写js验证
///
/// 3).数据验证,方法:IValidatableObject
/// 这个接口是为了实现Model的自验证(self-validating)的,是asp.net mvc3 新增的验证特性。
/// 这个特性和普通数据注解的区别是普通数据注解仅仅只能验证Model的一个属性,
/// 而实现了IValidatableObject接口的自验证则在Model的级别来验证
///
/// 4).人工验证
/// </remarks>
//[Bind(Include = "Id, Name, City, Country, Rating")]
public class RestaurantReview : IValidatableObject // ,IDataErrorInfo
{
Dictionary<string, string> _errorDictionary = new Dictionary<string, string>(); private int rating; public int Id { get; set; } [Required(ErrorMessage = "{0}不能为空!LoL")]
[Display(Name = "名称")]
[Remote("CheckNameIsExisted", "Account", HttpMethod = "POST",ErrorMessage = "该用户名已经被使用")]
[MaxWords(10,ErrorMessage = "{0}你输入的太多了")]
//[StringLength(6)]
public string Name { get; set; } [Required]
[Display(Name = "城市")]
[DisplayFormat(NullDisplayText = "N/A")]
public string City { get; set; } [Required]
[Display(Name = "国家")]
[DisplayFormat(NullDisplayText = "N/A")]
public string Country { get; set; } [Required(AllowEmptyStrings = true)]
[Display(Name = "等级")]
[Range(typeof(int),"1","10",ErrorMessage = "{0}必须是{1}和{2}之间的数字")]
public int Rating
{
get { return this.rating; }
set
{
bool IsNum = Regex.IsMatch(Convert.ToString(value), @"^\d+$");
if (!IsNum)
{
_errorDictionary.Add("Rating", "等级必须是数字");
}
this.rating = value;
}
} #region IDataErrorInfo Members /// <summary>
/// 获取一个单一的错误消息,指示对象实例中的错误
/// </summary>
public string Error
{
get
{
return string.Empty; //这里没有实现,返回值一个空的错误消息 }
} /// <summary>
/// 用来接收参数和返回一个错误消息指示属性中的错误
/// </summary>
/// <param name="columnName"></param>
/// <returns></returns>
/// <remarks>
/// 服务端再次进行数据验证(原则:永远不要相信客户端)。
/// /UpadtaModel(model)时会调用该方法,
/// 如果返回值不为string.Empty,该返回值会被添加到ModelState.Values属性中,并将
/// ModelState.IsValid设置为false。
/// </remarks>
public string this[string columnName]
{
get
{ if (_errorDictionary.ContainsKey(columnName))
{
return _errorDictionary[columnName];
}
return string.Empty;
}
} #endregion #region IValidatableObject
/// <summary>
///
/// </summary>
/// <param name="validationContext"></param>
/// <returns>返回类型是 IEnumerable(一种迭代器接口类型)</returns>
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Rating < 2 && Name.ToLower().Contains("hacker"))
{
yield return new ValidationResult("sorry,Mr.Hacker, you can't do this", new[] { "Name" });
}
} #endregion
}
}
        [HttpPost]//Controller中也可以直接接受Post的请求,可以不需要[Httppost]注释:
public ActionResult CheckNameIsExisted(string name)
{
bool result = name != "admin";
return Json(result, JsonRequestBehavior.AllowGet);
}

ReviewsController.cs

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Configuration;
using System.Data.Objects.DataClasses;
using System.Linq;
using System.Security.Cryptography;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using Kwin.AspNetMvc.OdeToFood.Models;
using PagedList; namespace Kwin.AspNetMvc.OdeToFood.Controllers
{
public class ReviewsController : Controller
{ #region 测试数据, private static bool _hasInitilizeDatas = false;
private static List<RestaurantReview> _restaurantReviews = new List<RestaurantReview>()
{
new RestaurantReview()
{
Id = ,
Name = "Cidde ha",
City = "shanghai",
Country = "China",
Rating =
}, new RestaurantReview()
{
Id = ,
Name = "张氏",
City = "shanghai",
Country = "中国",
Rating =
}, new RestaurantReview()
{
Id = ,
Name = "Bill",
City = "shanghai",
Country = "UK",
Rating =
}, new RestaurantReview()
{
Id = ,
Name = "Stevien Jason",
City = "shanghai",
Country = "英国",
Rating =
}
}; #endregion public ReviewsController()
{
if (!_hasInitilizeDatas)
{
InitilizeDatas();
_hasInitilizeDatas = true;
}
} /// <summary>
/// 模拟数据
/// </summary>
private static void InitilizeDatas()
{
Random random = new Random();
for (int i = ; i < ; i++)
{
RestaurantReview restaurantReview = new RestaurantReview()
{
Id = i,
Name = string.Format("老字号餐馆{0}", Convert.ToString(i)),
//City = "N/A",
//Country = "N/A",
Rating = random.Next(, )
}; _restaurantReviews.Add(restaurantReview);
}
} public static bool HasInitilizeDatas
{
get { return _hasInitilizeDatas; }
set { _hasInitilizeDatas = value; }
} public ActionResult Index(string searchKey = null, int currentPage = )
{
int pageSize = Convert.ToInt32(ConfigurationManager.AppSettings["pageSzie"]); var model = _restaurantReviews.Where(
r => searchKey == null || r.Name.ToLower().Contains(searchKey.ToLower().Trim()))
.OrderByDescending(r => r.Rating)
.Select(r => new RestaurantReview()
{
City = r.City,
Country = r.Country,
Id = r.Id,
Name = r.Name,
Rating = r.Rating
})
.ToPagedList(currentPage, pageSize); //局限:使用ToPagedList后不能用@Html.DisplayNameFor(model => model.Name)
//.ToList(); if (Request.IsAjaxRequest())
{
System.Threading.Thread.Sleep( * );//模拟处理数据需要的时间 //return View(model)会返回整个页面,所以返回部分视图。
return PartialView("_RestaurantPatialView", model);
}
return View(model);
} //
// GET: /Reviews/Details/5
/// <summary>
///关于使用System.Web.Mvc.Ajax的说明:
/// Controller的Action方法:
/// (1)当显式添加[HttpPost],传给System.Web.Mvc.Ajax的AjaxOptions()的HttpMethod只能为 "post",
/// (2)当显式添加[HttpGet],传给System.Web.Mvc.Ajax的AjaxOptions()的HttpMethod只能为 "get",
/// (3) 当都没有显式添加[HttpPost]和[HttpGet],传给System.Web.Mvc.Ajax的AjaxOptions()的HttpMethod可以为 "get"也可以为"post",
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public ActionResult Details(int id=)
{
var model = (from r in _restaurantReviews
where r.Id == id
select r).FirstOrDefault(); if (Request.IsAjaxRequest())
{
return PartialView("_RestaurantDetails", model);
} return View(model);
} //
// GET: /Reviews/Create public ActionResult Create()
{
return View();
} //
// POST: /Reviews/Create
#region FormColletion
//[HttpPost]
//[ValidateAntiForgeryToken] //public ActionResult Create(FormCollection collection)
//{
// try
// {
// // Add insert logic here
// if (ModelState.IsValid)
// {
// var model = new RestaurantReview();
// TryUpdateModel(model);
// _restaurantReviews.Add(model);
// } // return RedirectToAction("Index");
// }
// catch
// {
// return View();
// }
//}
#endregion #region 使用ViewModel实体 /// <summary>
///
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
/// <remarks>
/// ValidateAntiForgeryToken特性:
/// 与View中的@Html.AntiForgeryToken()一起使用,
/// 防止CSRF攻击
/// </remarks>
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(RestaurantReview model)
{
try
{
if (ModelState.IsValid)
{
_restaurantReviews.Add(model);
} return RedirectToAction("Index", "Reviews");
}
catch (Exception)
{ return View();
}
} #endregion //
// GET: /Reviews/Edit/5 public ActionResult Edit(int id)
{
return View();
} //
// POST: /Reviews/Edit/5 #region 使用FormCollection //[HttpPost]
//[ValidateAntiForgeryToken] //与View中的@Html.AntiForgeryToken()一起使用
//public ActionResult Edit(int id, FormCollection collection)
//{
// try
// {
// if (ModelState.IsValid)
// {
// //Add update logic here
// var model = (from r in _restaurantReviews
// where r.Id == id
// select r).First();
// TryUpdateModel(model);
// } // return RedirectToAction("Index");
// }
// catch
// {
// return View();
// }
//} #endregion #region 使用ViewModel实体 /// <summary>
///
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
/// <remarks>
/// 1.ValidateAntiForgeryToken特性:
/// 与View中的@Html.AntiForgeryToken()一起使用,
/// 防止CSRF攻击
///
///2. Bind(Include="....")是用来解决Mass Assignment漏洞
/// Mass Assignment漏洞演示:
/// 去掉Bind(Include="....")
/// public ActionResult Edit(RestaurantReview model)
///
/// 去掉Edit.cshtml中的某个字段如:Name的编辑标签(即:让用户无法修改Name字段)
/// <div class="editor-field">
/// @Html.EditorFor(model => model.Name)
/// @Html.ValidationMessageFor(model => model.Name)
/// </div>
///
/// 输入如下连接:
/// Edit方法路由后面添加参数?Name=hack(Name会自动绑定到模型绑定类型RestaurantReview中的Name属性),
/// 即:
/// http://localhost:3951/Reviews/Edit/1003?Name=hack
///
/// 经测试无上面的Mass Assignment,可能新aspnet mvc已经修复该bug
/// </remarks>
[HttpPost]
[ValidateAntiForgeryToken]
//public ActionResult Edit([Bind(Include = "Id, Name,City, Country, Rating")]RestaurantReview model)
public ActionResult Edit(RestaurantReview model)
{
try
{ if (ModelState.IsValid)
{
var restaurentView = (from r in _restaurantReviews
where r.Id == model.Id
select r).FirstOrDefault();
TryUpdateModel(restaurentView); return RedirectToAction("Index");
} return View();
}
catch (Exception ex)
{
ModelState.AddModelError("", ex.Message); //给View返回一个错误消息,最终交给@Html.ValidationSummary显示。
return View();
} } #endregion
//
// GET: /Reviews/Delete/5 public ActionResult Delete(int id)
{
var models = from restaurantReview in _restaurantReviews
where restaurantReview.Id == id
select restaurantReview; var model = models.First(); //var model = _restaurantReviews.SingleOrDefault(); return View(model);
} //
// POST: /Reviews/Delete/5 [HttpPost]
[ValidateAntiForgeryToken] public ActionResult Delete(int id, FormCollection collection)
{
try
{
// Add delete logic here
var model = (from r in _restaurantReviews
where r.Id == id
select r).First();
_restaurantReviews.Remove(model); return RedirectToAction("Index");
}
catch
{
return View();
}
} [HttpPost]//Controller中也可以直接接受Post的请求,可以不需要[Httppost]注释:
public ActionResult CheckNameIsExisted(string name)
{
bool result = name != "admin";
return Json(result, JsonRequestBehavior.AllowGet);
} [ChildActionOnly]
public ActionResult BestReview()
{
var starRestaurent = from r in _restaurantReviews
where r.Rating >=
orderby r.Rating descending
select r; return PartialView("_BestReview", starRestaurent.Take());
} /// <summary>
///
/// </summary>
/// <param name="term"></param>
/// <returns>
///
//http://localhost:3951/Reviews/autocompleted?term=老
//返回JSON,如下格式:
// [
// {"label":"老字号餐馆1000"},{"label":"老字号餐馆1001"},{"label":"老字号餐馆1002"},
// {"label":"老字号餐馆1003"},{"label":"老字号餐馆1004"},{"label":"老字号餐馆1005"},
// {"label":"老字号餐馆1006"},{"label":"老字号餐馆1007"},{"label":"老字号餐馆1008"},{"label":"老字号餐馆1009"}
// ]
/// </returns>
public ActionResult AutoCompleted(string term)
{
var model = _restaurantReviews.Where(r=>r.Name.ToLower().Contains(term.ToLower().Trim()))
.Take()
.Select(r=> new
{
label = r.Name //匿名对象的字段名必须是label,因为ui.item.label
}); //serialize model into JSON format return Json(model,JsonRequestBehavior.AllowGet);
}
}
}
Index.cshtml
@*@model IEnumerable<Kwin.AspNetMvc.OdeToFood.Models.RestaurantReview>*@
@model IPagedList<RestaurantReview> @{
ViewBag.Title = "我的推荐:餐厅系列";
ViewBag.Message = "美味不断,吃遍填下";
} @section mytile{
<section class="featured">
<div class="content-wrapper">
<hgroup class="title">
<h1>@ViewBag.Title</h1><br/>
<h2>@ViewBag.Message</h2>
</hgroup>
<p> </p>
</div>
</section>
} @* -----------.不用Ajax--------------------------- *@
@*<form method="post">
<input type="search" name="searchKey"/>
<input type="submit" value="按名称搜索"/>
</form>*@ @* -----------.System.Web.Mvc.Ajax---------------------------
不用手工为Form添加属性标签,MVC框架自己添加了, Ajax.BeginForm生成的标签如下:
<form id="form0" method="post"
data-ajax-url="/Reviews"
data-ajax-update="#restaurantList"
data-ajax-mode="replace"
data-ajax-method="post"
data-ajax-loading-duration=""
data-ajax-loading="#loding"
data-ajax="true"
action="/Reviews" novalidate="novalidate">
------------------------------------------------------------------*@
@*@using (Ajax.BeginForm(
new AjaxOptions()
{
HttpMethod = "post",
Url = @Url.Action("Index","Reviews"),
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "restaurantList",
LoadingElementId = "loding",
LoadingElementDuration =
}))
{ <input type="search" name="searchKey"/>
<input type="submit" value="按名称搜索"/> }*@ @* -----------.Ajax--------------------------------------
需要手工为Form添加些属性标签,用于锚点
模仿MVC框架的构建自己的“非介入式Javascript”模式 生成的form为:
<form data-otf-ajax-updatetarget="#restaurantList"
data-otf-ajax="true"
action="/Reviews"
method="post"
novalidate="novalidate">
-------------------------------------------------------*@
<form method="post"
action="@Url.Action("Index")"
data-otf-ajax="true"
data-otf-ajax-updatetarget="#restaurantList">
<input type="search" name="searchKey" data-oft-ajax-autocompleted="@Url.Action("AutoCompleted")" />
<input type="submit" value="按名称搜索" />
</form> <p>
<strong>
@if (User.Identity.IsAuthenticated)
{
Html.ActionLink("添加餐馆(登录后)", "Create");
}
@Html.ActionLink("添加餐馆", "Create")
</strong> </p> <div id="loding" hidden="hidden">
<img class="smallLoadingImg" src="@Url.Content("~/Content/images/loading.gif")" />
</div>
@*Ajax异步更新目标html元素*@
<div id="restaurantList">
@{
if (@Model.Any() )
{
//用Html.Partial出现不能显示的问题。原因待查,改用Html.RenderPartial
Html.RenderPartial("_RestaurantPatialView",Model);
}
else
{
<img src="@Url.Content("~/Content/images/NotFoundCryingFace.jpg")"/>
<strong><font color="red">抱歉,没有任何餐馆</font></strong>
}
}
</div>

odf.js

$(function () {

    /*ajaxFrom*/
var ajaxFormSubmit = function() {
var $form = $(this);
var ajaxOption = {
type: $form.attr("method"),
url: $form.attr("action"),
data: $form.serialize()
}; $.ajax(ajaxOption).done(function(data) {
var updateTargetId = $form.attr("data-otf-ajax-updatetarget");
var $updateTarget = $(updateTargetId);
if ($updateTarget.length > 0) { var $returnHtml = $(data);
$updateTarget.empty().append(data);
$returnHtml.effect("highlight",6000);
}
}); return false;
}; $("form[data-otf-ajax='true']").submit(ajaxFormSubmit); /*AutoCompleted*/
var submitAutoCompleted = function(event, ui) {
var $input = $(this);
$input.val(ui.item.label); var $form = $input.parents("form:first");
$form.submit();
}; var createAutoCompleted = function() {
var $input = $(this);
var ajaxOption = {
source: $input.attr("data-oft-ajax-autocompleted"), //告诉AutoComplete组件去哪里获取数据
select: submitAutoCompleted, //选择某选项后,要处理的逻辑
}; $input.autocomplete(ajaxOption);
} $("input[data-oft-ajax-autocompleted]").each(createAutoCompleted); /*分页,使用 PagedList.dll,*/
var getPage = function() {
var $a = $(this);
var ajaxOption = {
url: $a.attr("href"),
type: "post",
data:$("form").serialize() //把表单中的数据也一起提交,比如,搜索关键字。结果是:在搜索关键字得到的结果上进行分页。 }; $.ajax(ajaxOption).done(function(data) {
var updateTargetId = $a.parents("div.pagedList").attr("data-otf-ajax-updatetarget");
var $updateTarget = $(updateTargetId);
var $returnHtml = $(data);
$updateTarget.empty().append(data);
$returnHtml.effect("highlight", 6000);
}); return false;
} $(".main-content").on("click", ".pagedList a",getPage); //$("#Pagination").pagination(); });

Review.js

$(function() {
$(".isStar").prepend('<del class="hot"></del>');
//$(".isStar").append('<del class="hot"></del>');
});

-----------------------------------

TestAcitonResultController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Kwin.AspNetMvc.OdeToFood.Models; namespace Kwin.AspNetMvc.OdeToFood.Controllers
{
public class TestAcitonResultController : Controller
{
[ActionName("RedirectToAction")] //为Action起了别名RedirectToAction,就不能用原来的TestRedirectToAction访问了
public ActionResult TestRedirectToAction()
{
TestModel myTestModel = new TestModel()
{
IntParement = ,
StringPrarement = "TestModel->StringPrarement"
}; return RedirectToAction("TextRouteParement",
"TestAcitonResult",
new
{
id = "",
myparementString = "keasy5",
myparementInt = ,
testModel = myTestModel
});
} public ActionResult TestRedirectToRoute()
{
return RedirectToRoute("Default", new { controller = "Home", action = "ForTestIndex", myparement = "keasy5" });
} public ActionResult TestFile()
{
return File(Server.MapPath("~/Content/site.css"), "text/css");
} public ActionResult TestJson()
{
return Json(new {Name = "Keasy5", Laction = "China"},JsonRequestBehavior.AllowGet);
} public ActionResult TextRouteParement(string myparementString, int myparementInt, [ModelBinder(typeof(TestModelModelBinder))]TestModel testModel)
{
var controller = RouteData.Values["controller"];
var action = RouteData.Values["action"];
var id = RouteData.Values["id"]; var myparement2 = RouteData.Values["myparement"];
var myparementForRequestForm = Request["myparement"]??"N/A"; var messages = string.Format("controller={0},action={1},id={2},myparementString={3}, myparementInt={4},RouteData.Values[\"myparement\"]={5},testModel.IntParement={6},testModel.StringPrarement={7},Request[\"myparement\"]={8}",
controller, action, id, myparementString, myparementInt, myparement2, testModel.IntParement, testModel.StringPrarement, myparementForRequestForm); return Content(messages);
} }
}

TestModelModelBinder.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using ModelBindingContext = System.Web.Http.ModelBinding.ModelBindingContext; namespace Kwin.AspNetMvc.OdeToFood.Models
{
public class TestModelModelBinder : DefaultModelBinder
{ #region IModelBinder Members
public override object BindModel(ControllerContext controllerContext, System.Web.Mvc.ModelBindingContext bindingContext)
{
HttpRequestBase requestBase = controllerContext.HttpContext.Request;
TestModel testModel = new TestModel(); testModel.IntParement = Convert.ToInt32(requestBase["IntParement"]);
testModel.StringPrarement = requestBase["IntParement"]; /* var value = base.BindModel(controllerContext, bindingContext);
if (bindingContext.ModelType == typeof(TestModel))
return value;*/ return testModel; }
#endregion }
}

--------------------------------------------------

TestActionFilerController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Kwin.AspNetMvc.OdeToFood.Filters; namespace Kwin.AspNetMvc.OdeToFood.Controllers
{
//[HandleError]可以注释掉因为在FilterConfig已经定义了全局的HandleError,为所有的controller使用了[HandleError]
public class TestActionFilterController : Controller
{
public ActionResult Index()
{
string messages = "TestActionFilterController index"; return Content(messages);
} [Authorize]
public ActionResult TestAuthorize()
{
string messages = "TestAuthorize"; return Content(messages);
} public ActionResult TestHandleError(int id = )
{
if (id == )
{
throw new Exception("我是故意的,测试HandleError用的异常!");
}
/* 默认是显示一个bug异常页面,
* 要是定制显示bug页面,先在web.cofng配置如下节点:
* <system.web>
<customErrors mode="On"/>*/
return Content("TestHandleError");
} public ActionResult TestLog()
{
return Content("TestHandleError"); }
}
}

---------------------------------------------------

FilterConfig.cs

using System.Web;
using System.Web.Mvc;
using Kwin.AspNetMvc.OdeToFood.Filters; namespace Kwin.AspNetMvc.OdeToFood
{
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{ //在这里定义的是全局的Filter,意味着所有的Controller都启用这里注册的Filter filters.Add(new HandleErrorAttribute());
filters.Add(new LogAttribute());
}
}
}

Filters/LogAttribute.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http.Controllers;
using System.Web.Mvc; namespace Kwin.AspNetMvc.OdeToFood.Filters
{ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class LogAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
//写系统日志
} public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
} public override void OnResultExecuted(ResultExecutedContext filterContext)
{
base.OnResultExecuted(filterContext);
} public override void OnResultExecuting(ResultExecutingContext filterContext)
{
base.OnResultExecuting(filterContext);
} }
}