Can't update image in ASP.NET Core MVC - asp.net-core

I'm beginner to ASP.NET Core, I am trying to update an image in a database, I have some doubts about my code, image added to database but cannot update
AdminController
public async Task<ViewResult> Edit(int productId)
{
return View(await _repository.Products
.FirstOrDefaultAsync(p => p.ProductID == productId));
}
[HttpPost]
public async Task<IActionResult> Edit(Product product, IFormFile image)
{
if (ModelState.IsValid)
{
if(image != null && image.Length > 0)
{
var fileName = Path.GetFileName(image.FileName);
var filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot\\images\\items", fileName);
using(var fileSteam = new FileStream(filePath, FileMode.Create))
{
await image.CopyToAsync(fileSteam);
}
product.Image = fileName;
}
_repository.SaveProduct(product);
TempData["message"] = $"{product.Name} has been edit";
return RedirectToAction("Index");
}
else
{
return View(product);
}
}
Edit.cshtml
<form asp-action="Edit" method="post" enctype="multipart/form-data">
<input type="hidden" asp-for="ProductID" />
<div class="form-group">
<h4><label asp-for="Image"></label></h4>
<input asp-for="Image" type="file" class="form-control" />
</div>

I'm found where I was wrong, thanks Xing Zou! I updated the SaveProduct(product)
public Product SaveProduct(Product productDataUpdate)
{
var prod = _context.Products.Attach(productDataUpdate);
prod.State = Microsoft.EntityFrameworkCore.EntityState.Modified;
_context.SaveChanges();
return productDataUpdate;
}

Related

Serving dynamic XML file from .NET Core Web Application

In my .NET Core 2.2 website, I have a controller method within my BlogController that generates a sitemap.xml file:
public ActionResult SiteMap()
{
// logic here
return Content("<sitemap>...</sitemap>", "text/xml");
}
I have this route set up so that the sitemap will be output at https://mysite/sitemap
routes.MapRoute(
name: "sitemap",
template: "sitemap",
defaults: new { controller = "Blog", action = "SiteMap" });
That works, in that accessing /sitemap results in the XML content being served up.
However, when I access https://mysite/sitemap.xml, I get a 404 error.
I'm pretty sure this is something to do with static file handling, but I'm not sure how to set it up so that /sitemap.xml works.
You could try to dynamtically build xml like this
[Route("/sitemap.xml")]
public void SitemapXml()
{
string host = Request.Scheme + "://" + Request.Host;
Response.ContentType = "application/xml";
using (var xml = XmlWriter.Create(Response.Body, new XmlWriterSettings { Indent = true }))
{
xml.WriteStartDocument();
xml.WriteStartElement("urlset", "http://www.sitemaps.org/schemas/sitemap/0.9");
xml.WriteStartElement("url");
xml.WriteElementString("loc", host);
xml.WriteElementString("changefreq", "daily");
xml.WriteElementString("lastmod", DateTime.Now.ToString("yyyy-MM-dd"));
xml.WriteEndElement();
var categories = _categoryService.GetAllCategories(inclTopMenu: true);
foreach (var c in categories)
BuildXml(xml, c.GetSeName(), host);
xml.WriteEndElement();
}
}
private void BuildXml(XmlWriter xml, string url, string host)
{
xml.WriteStartElement("url");
xml.WriteElementString("loc", host + "/" + url);
xml.WriteElementString("changefreq", "weekly");
xml.WriteElementString("lastmod", DateTime.Now.ToString("yyyy-MM-dd"));
xml.WriteEndElement();
}
or create a page like https://forums.asp.net/t/2160949.aspx?Create+Robots+txt+and+sitemap+xml+dynamically+in+asp+net+core
Here is a simple demo about how to generate xml file to server and show xml file by using url like https://mysite/sitemap.xml:
1.View:
<form asp-action="Create" enctype="multipart/form-data">
<div class="form-group">
<input type="file" name="file" id="file" class="form-control" />
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
2.Controller:
public class UsersController : Controller
{
private IHostingEnvironment _env;
public UsersController(IHostingEnvironment env)
{
_env = env;
}
[HttpGet]
public IActionResult Create()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Create(IFormFile file)
{
var fileName = System.IO.Path.GetFileName(file.FileName);
var filePath = System.IO.Path.Combine(_env.WebRootPath, fileName);
if (file.Length > 0)
{
using (var stream = new FileStream(filePath, FileMode.Create))
{
await file.CopyToAsync(stream);
}
}
return View("Index");
}
}
3.Be sure to add app.UseStaticFiles(); like below,then you could access https://mysite/sitemap.xml:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//...
app.UseStaticFiles();
//...
}
4.Result:

Add section comments to my Details view

in my View I have something like this:
<div class="form-group">
<form asp-controller="Ticket" asp-action="Comment" method="post">
<label asp-for="Comment" class="control-label"></label>
<textarea asp-for="Comment.Content" class="form-control" placeholder="Add comment!"></textarea>
<span asp-validation-for="Comment.Content" class="text-danger"></span>
</form>
<input type="submit" value="Add comment" class="btn btn-default" />
This is in my Details View. Now I want to add comment to my model
public class TicketCommentViewModel
{
public Ticket Ticket { get; set; }
public Comment Comment { get; set; }
}
and Controller:
public async Task<IActionResult> Comment(TicketCommentViewModel model)
{
var ticket = await _context.Tickets.FirstOrDefaultAsync(u => u.TicketId == model.Ticket.TicketId);
var user = await GetCurrentUserAsync();
if(ticket == null)
{
return NotFound();
}
model.Comment.SendTime = DateTime.Now;
model.Comment.TicketID = ticket.TicketId;
model.Comment.Ticket = ticket;
model.Comment.UserId = user.Id;
model.Comment.User = user;
_context.Comments.Add(model.Comment);
ticket.Comments = await _context.Comments.ToListAsync();
return View();
}
I have problem with this -> How to go from first code (add Comment) to Controller, and add my Comment to DB.
Can someone help me with that ?
Thanks.
I am not sure what is the problem but I guess that when you click submit your data is not submitted. This is because your submit button is outside the <form>. Try move the submit button inside the <form>
//Two action methods in the controller
public ActionResult AddComment(int PageId, string name, string email, string comment)
{
Comment comment = new Comment()
{
PageID = PageId,
Name = name,
Email = email,
Comment = comment,
CreateDate = DateTime.Now
};
DbContext.Add(jobOffer);
return PartialView("ShowComments", DbContext.Where(c=> c.pageID == PageId));
}
public ActionResult ShowComments(int PageId)
{
return PartialView(DbContext.Where(c=> c.pageID == PageId));
}
//Add the script after the comment div in the View
<script>
function addComment() {
$.ajax({
url: "/Comment/AddComment/"+#Model.PageID,
type: "Get",
data: {
name: $("#txtName").val(), email: $("#txtEmail").val(),
comment : $("#txtComment").val() }
}).done(function(result) {
$("#offerList").html(result);
$("#txtName").val("");
$("#txtEmail").val("");
$("#txtComment").val("");
});
}
</script>

ASP.Net MVC - cannot set value of #Html.Checkbox after changing dropdownlist

I've looking all over for something similar, couldn't find nothing..
I'm using ASP.NET MVC 4. I'm building a page so the users in my app can manage the permissions associated with each role.
So i have a view with #htmlDropDownList to show all the available roles, and below, one #Html.CheckBox for each Permission of the role wich is selected above.
The first time the view is rendered, the checkboxes are all set to true or false, according to the permission of that role.All is fine, life is good :) . When the value of the drop is changed, i post the SelectedRoleId using $.ajax. Then, i fetch all the permissions of the new selected role.
While in debug, in the razor view, i can confirm the new values (true or false) inside the model are correct. The problem is that the checkboxes show the old values, before the role was changed..
This is my first question asked, so i'll have to apologize if the question is not being made the best way.
And thx in advance to all of you :)
So here's my Controller:
public ActionResult Index(int ? SelectedRoleId)
{
ManagePermissionsViewModel model = new ManagePermissionsViewModel();
if (SelectedRoleId == null)
{
model.SelectedRoleID = 1; // value 1 is the supervisor Role
}
else
{
model.SelectedRoleID = SelectedRoleId;
}
//values for the dropdownlist of Roles
var items = from x in db.UserRoles
select x;
model.RoleList = new SelectList(items, "Id", "DESCRIPTION");
//gets all the permissions of the selected role
model.EntirePermissionList = (from k in db.Permissions
select new Permission
{
IdPermission = k.Id,
PermissionDescription = k.Description,
IsSet = db.RolePermissions.Any(n => n.RoleId == model.SelectedRoleID && n.PermissionId == k.Id),
PermissionGroupId = (int)k.PermissionGroupId
}).ToList();
//Gets all the groups of Permissions
model.ListPermissionGroups = (from l in db.PermissionGroups
select new PermissionGroup
{
Id = l.Id,
Description = l.Description
}).ToList();
return View(model);
}
[HttpPost]
public ActionResult Index(FormCollection form) {
switch (form["SubmitButton"])
{
case "Save":
SavePermissions();
break;
default:
return RedirectToAction("Index", new RouteValueDictionary(new { controller = "ManagePermissions", action = "Index", SelectedRoleId = Convert.ToInt32(form["SelectedRoleId"]) }));
}
return View();
}
And here is my View:
'#model AML.Web.Models.ManagePermissionsViewModel
#using (Html.BeginForm("Index", "ManagePermissions", FormMethod.Post, new { id = "MyForm" }))
{
#Html.Label("Role :", htmlAttributes: new { #class = "control-label col-md-2" })
#Html.DropDownList("RoleId", Model.RoleList, new { id = "RoleId" })
<div>
#foreach (var item in Model.ListPermissionGroups)
{
<h3> #item.Description</h3>
foreach (var permission in Model.EntirePermissionList.Where(n => n.PermissionGroupId == item.Id))
{
<h5>
#permission.PermissionDescription
#Html.CheckBox("Chk_Permisssion", permission.IsSet)
</h5>
}
}
</div>
<input type="submit" value="Save" name="SubmitButton" class="btn btn-default" />
}
#section Scripts {
<script type="text/JavaScript">
$(document).ready(function () {
$("#RoleId").change(function (e) {
e.preventDefault();
$.ajax({
url: "/ManagePermissions/Index",
cache: false,
type: "POST",
data: { 'SelectedRoleId': $(this).val() },
dataType: "json",
success: function (result) { console.log("Sucess!"); },
error: function (error) { console.log("Error!"); }
})
});
});
</script>
}
And my viewModel:
public class ManagePermissionsViewModel
{
public int? SelectedRoleID { get; set; }
public string SelectedRoleDescription { get; set; }
public SelectList RoleList { get; set; }
public List<Permission> EntirePermissionList { get; set; }
public List<PermissionGroup> ListPermissionGroups { get; set; }
}
public class Permission
{
public int IdPermission { get; set; }
public bool IsSet { get; set; }
public string PermissionDescription { get; set; }
public int PermissionGroupId { get; set; }
}
public class PermissionGroup {
public int Id { get; set; }
public string Description{ get; set; }
}
UPDATE 1 -
Well, i think i got it. Let me post my approach
In the View:
#Html.DropDownListFor(n => n.SelectedRoleID, Model.RoleList,null,
new { onchange = "document.location.href = '/ManagePermissions/Index?SelectedRoleId=' + this.options[this.selectedIndex].value;" })
<div>
#foreach (var item in Model.ListPermissionGroups)
{
<h3> #item.Description</h3>
foreach (var permission in Model.EntirePermissionList.Where(n => n.PermissionGroupId == item.Id))
{
<h5>
#permission.PermissionDescription
<input type="checkbox" id="#permission.IdPermission" checked="#permission.IsSet">
</h5>
}
}
</div>
And in the Controller:
public ActionResult Index(int? SelectedRoleId)
{
ManagePermissionsViewModel model = new ManagePermissionsViewModel();
ModelState.Clear();
if (SelectedRoleId == null)
{
model.SelectedRoleID = 1;
}
else
{
model.SelectedRoleID = SelectedRoleId;
}
var items = from x in db.UserRoles
select x;
model.RoleList = new SelectList(items, "Id", "DESCRIPTION");
model.EntirePermissionList = (from k in db.Permissions
select new Permission
{
IdPermission = k.Id,
PermissionDescription = k.Description,
IsSet = db.RolePermissions.Any(n => n.RoleId == model.SelectedRoleID && n.PermissionId == k.Id),
PermissionGroupId = (int)k.PermissionGroupId
}).ToList();
model.ListPermissionGroups = (from l in db.PermissionGroups
select new PermissionGroup
{
Id = l.Id,
Description = l.Description
}).ToList();
ModelState.Clear();
return View(model);
}
Now each time the Drop changes value, the permissions in the checkboxes are updated. I got it to work with the attribute on the drop, "on change = Document.location.hef = URL". Is this a good approach? Or should i use something like ajax request ?
UPDATE 2
The Controller:
public async Task<ActionResult> Index(int? SelectedRoleId)
{
if (SelectedRoleId == null)
{
SelectedRoleId = 1;
}
var model = await GetSelectedPermissions(SelectedRoleId);
return this.View("Index",model);
}
[HttpGet]
public async Task<ActionResult> GetPermissions(string Id)
{
var SelectedRoleId = int.Parse(Id);
var model = await this.GetSelectedPermissions(SelectedRoleId);
return PartialView("_ManagePermissions", model);
}
private async Task<ManagePermissionsViewModel> GetSelectedPermissions(int? SelectedRoleId)
{
ModelState.Clear();
ManagePermissionsViewModel model = new ManagePermissionsViewModel();
model.SelectedRoleID = SelectedRoleId;
var items = from x in db.UserRoles
select x;
model.RoleList = new SelectList(items, "Id", "DESCRIPTION");
model.EntirePermissionList = await (from k in db.Permissions
select new Permission
{
IdPermission = k.Id,
PermissionDescription = k.Description,
IsSet = db.RolePermissions.Any(n => n.RoleId == model.SelectedRoleID && n.PermissionId == k.Id),
PermissionGroupId = (int)k.PermissionGroupId
}).ToListAsync();
model.ListPermissionGroups = await (from l in db.PermissionGroups
select new PermissionGroup
{
Id = l.Id,
Description = l.Description
}).ToListAsync();
return model;
}
The View
<h2>Permissions - Ajax with Partial View</h2>
#using (Html.BeginForm("SaveData", "ManagePermissions", FormMethod.Post, new { id = "MyForm" }))
{
#Html.Label("Role :", htmlAttributes: new { #class = "control-label col-md-2" })
#Html.DropDownListFor(n => n.SelectedRoleID, Model.RoleList, null, null)
<div id="target">
#Html.Partial("~/Views/Shared/_ManagePermissions.cshtml", Model)
</div>
<input type="submit" value="Save" name="SubmitButton" class="btn btn-default" />
}
#section Scripts {
<script type="text/javascript">
$(document).ready(function () {
$("#SelectedRoleID").change(function () {
var SelectedRoleID = $("#SelectedRoleID").val();
$("#target").load('#(Url.Action("GetPermissions","ManagePermissions",null, Request.Url.Scheme))?Id=' + SelectedRoleID);
});
});
</script>
}
And the Partial View:
<div>
#foreach (var item in Model.ListPermissionGroups)
{
<h3> #item.Description</h3>
foreach (var permission in Model.EntirePermissionList.Where(n => n.PermissionGroupId == item.Id))
{
<h5>
#permission.PermissionDescription
<input type="checkbox" id="#permission.IdPermission" checked="#permission.IsSet">
</h5>
}
}
</div>

(ModelState.IsValid) Property is not working properly in asp.net mvc4 with entity framework

I have tried simple user log in using asp.net mvc4. I have used this condition (ModelState.IsValid), It was workiing before two days. Now i am trying to execute this program, But that property is terminating the condition. Please anyone help me to rectify this problem.
This is my controller code
{
[HttpPost]
[AllowAnonymous]
public ActionResult LogIn(Project.Models.Tbl_Users user)
{
int userid = user.UserID;
var sessionid = Session["userid"];
Session["RoleId"] = user.RoleId;
Session["Username"] = user.UserName;
var sessionval = Session["Username"].ToString();
if (!ModelState.IsValid)
{
if (Isvalid(user.UserName, user.UserPassword))
{
var db = new Project.Models.EntitiesContext();
var userroleid = db.Tbl_Users.FirstOrDefault(u => u.UserName == user.UserName);
Session["RoleId"] = userroleid.RoleId;
int sessionroleid = Convert.ToInt32(Session["RoleId"]);
FormsAuthentication.SetAuthCookie(user.UserName, false);
string sessionusername = Session["Username"].ToString();
if (sessionroleid == 1)
{
return RedirectToAction("adminpage", "LogIn");
}
else
if(sessionroleid==2)
{
return RedirectToAction("teammanager", "LogIn");
}
else
{
return RedirectToAction("userpage", "LogIn");
}
}
return View(sessionval);
}
return View();
}
private bool Isvalid(string username, string password)
{
bool Isvalid = false;
using(var db = new Project.Models.EntitiesContext())
{
var user = db.Tbl_Users.FirstOrDefault(u => u.UserName == username);
var pass = db.Tbl_Users.FirstOrDefault(u => u.UserPassword == password);
if (username != null)
{
try
{
if (user.UserName == username)
{
if (pass.UserPassword == password)
{
Isvalid = true;
//Session["RoleId"] = user.RoleId;
//int sessionid = Convert.ToInt32(Session["RoleId"]);
}
}
}
catch
{
//Response.Write("Login Failed For The User");
Isvalid = false;
}
}
}
}
This is my model
{
[Required(ErrorMessage = "User Name is Invalid")]
[StringLength(200)]
[Display(Name = "User Name")]
public string UserName { get; set; }
[Required(ErrorMessage = "Password Field is Invalid")]
[StringLength(50, MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string UserPassword { get; set; }
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
This is my view code
{
<form method="post" id="signin" action="#Url.Action("LogIn", "LogIn")">
<body style="background-color: Gray;">
<div>
<div>
</div>
#if (!Request.IsAuthenticated)
{
<strong>#Html.Encode(User.Identity.Name)</strong>
#Html.ActionLink("Log Out", "LogOut", "LogIn")
}
else
{
<fieldset>
<div>#Html.LabelFor(u => u.UserName)</div>
<div>#Html.TextBoxFor(u => u.UserName)
#if (Request.IsAuthenticated)
{
#Html.ValidationMessageFor(u => u.UserName)
#*#Html Session["Username"] = #Html.TextBoxFor(u => u.UserName);*#
}
</div>
<div>#Html.LabelFor(u => u.UserPassword)</div>
<div>#Html.PasswordFor(u => u.UserPassword)
#Html.ValidationMessageFor(u => u.UserPassword)
</div>
<div>#Html.CheckBoxFor(u => u.RememberMe)
#Html.LabelFor(u => u.RememberMe, new { #class = "checkbox" })
</div>
<div>
#Html.ValidationSummary(true, "Login Failed")
</div>
<input type="submit" value="LogIn"/>
</fieldset>
}
</div>
</body>
</form>
}
please DEBUG your code.
past this code below, just above if(!ModelState.IsValid)
var propertiesWithErrors = ModelState.Where(state => state.Value.Errors.Any()).Select(state => state.Key);;
propertiesWithErrors will give you the list of properties that has validation errors.

MVC 4 multiple buttons in form - why isn't this code working

I am trying to use a variation of the code from this page:
Multiple button in MVC
But everytime I click on the buttons it goes to the index actionresult method and not one of the button methods. Index is the view name but the button clicks are happening in a partial view called "P_NewPersonForm.cshtml"
P_NewPersonForm.cshtml
#using (Html.BeginForm())
{
<div id="divClaimType">
#Html.Label("Claim type:")
#Html.DropDownListFor(m => m.modelClaim.ClaimType, new List<SelectListItem>
{
new SelectListItem{ Text="Legal", Value = "Legal" },
new SelectListItem{ Text="Immigration", Value = "Immigration" },
new SelectListItem{ Text="Housing", Value = "Housing" }
})
</div>
<div id="divClaimStatus" style="padding: 5px;">
#foreach(var item in Model.LinkerStatusOfClaim)
{
#Html.Label("Claim status:")
#Html.DropDownListFor(m => m.LinkerStatusOfClaim[0].ClaimStatusID, new SelectList(Model.modelClaimStatus, "ClaimStatusID", "ClaimStatus"))
#Html.LabelFor(m => m.LinkerStatusOfClaim[0].Notes)
#Html.TextAreaFor(m => m.LinkerStatusOfClaim[0].Notes)
#Html.LabelFor(m => m.LinkerStatusOfClaim[0].StartDate)
#Html.TextBoxFor(m => m.LinkerStatusOfClaim[0].StartDate, new { #id = "datepicker", #Value = DateTime.Now, #readonly = true, Style = "background:#cccccc;" })
<br />
#Html.ValidationMessageFor(model => model.LinkerStatusOfClaim[0].StartDate)
<br />
}
<input type="submit" value="Add another status to this claim..." name="action:add"/>
<input type="submit" value="Delete status." name="action:remove"/>
#* #Ajax.ActionLink("Add another status to this claim...", "AddClaim", "Client", new AjaxOptions { HttpMethod = "POST"})*#
</div>
}
</div>
I have one button for adding to the collection of claims and another to remove one from the collection.
ClientController
public ActionResult Index()
{
var Model = new modelPersonClaim();
// Add one item to model collection by default
LinkerStatusOfClaim LinkerStatusOfClaim = new LinkerStatusOfClaim();
Model.LinkerStatusOfClaim.Add(LinkerStatusOfClaim);
DataLayer.RepositoryClient RC = new RepositoryClient();
Model.isValidModel = true;
RC.GetClaimTypes(Model, PersonTypes.NewPerson.ToString());
return View(Model);
}
[HttpPost]
public ActionResult P_NewPersonForm(modelPersonClaim Model)
{
DataLayer.RepositoryClient RC = new RepositoryClient();
RC.GetClaimTypes(Model, PersonTypes.NewPerson.ToString());
Model.isValidModel = ModelState.IsValid;
if (ModelState.IsValid)
{
RC.CreatePerson(Model);
Model.SuccessfulInsert = true;
Model.InsertString = "Person data has been successfully inserted into the database.";
if (Model.modelClaim.ClaimMade)
{
RC.CreateClaim(Model);
}
}
else
{
Model.SuccessfulInsert = false;
Model.InsertString = "Person data could not be inserted into the database. Missing key fields.";
}
return View("Index", Model);
}
[AttributeUsage(AttributeTargets.Method, AllowMultiple=false, Inherited = true)]
public class MultiButtonAttribute : ActionNameSelectorAttribute
{
public string Name { get; set; }
public string Argument { get; set; }
public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
{
var isValidName = false;
var keyValue = string.Format("{0}:{1}", Name, Argument);
var value = controllerContext.Controller.ValueProvider.GetValue(keyValue);
if (value != null)
{
controllerContext.Controller.ControllerContext.RouteData.Values[Name] = Argument;
isValidName = true;
}
return isValidName;
}
}
[HttpPost]
[MultiButtonAttribute(Name = "action", Argument = "Add another status to this claim...")]
public ActionResult AddClaimStatus(modelPersonClaim Model)
{
Model.LinkerStatusOfClaim.Insert(Model.LinkerStatusOfClaim.Count, new LinkerStatusOfClaim());
return View("Index", Model);
}
[HttpPost]
[MultiButtonAttribute(Name = "action", Argument = "Delete status.")]
public ActionResult RemoveClaimStatus(modelPersonClaim Model)
{
// Can't remove IF only 1
if (Model.LinkerStatusOfClaim.Count == 1)
{
}
else
{
Model.LinkerStatusOfClaim.RemoveAt(Model.LinkerStatusOfClaim.Count);
}
return View("Index", Model);
}
When I click one the buttons it hits the public override bool IsValidName twice. Once for each button. But then because the action name is always index, it goes to the index method and not one of the button methods.
Does anyone have any ideas how to fix this?
Something is wrong with this part:
var keyValue = string.Format("{0}:{1}", Name, Argument);
var value = controllerContext.Controller.ValueProvider.GetValue(keyValue);
Your attribute is this:
[MultiButtonAttribute(Name = "action", Argument = "Add another status to this claim...")]
So in that case keyValue will become: "action:Add another status to this claim..." while your HTML states: <input type="submit" value="Add another status to this claim..." name="action:add"/>, so I think Argument in your attribute should be add.