should this mvc action be trimmed down - c#

I'm just getting into mvc 4 (and mvc in general) and am just wondering is this action code ok or should it be stripped down again?
[HttpPost]
public ActionResult Index(DashboardViewModel dbModel)
{
//retrieve latest resident order
var residentOrder = db.ResidentOrders.GetById(dbModel.ResidentOrderID);
if (residentOrder == null)
{
var order = db.Orders.GetById(dbModel.OrderID);
var user = db.Users.GetUserByUsername(User.Identity.Name);
residentOrder = new ResidentOrder()
{
CreatedDate=DateTime.Now,
LastUpdateDate = DateTime.Now,
Litres=0,
Customer = user
};
order.ResidentOrders.Add(residentOrder);
db.Commit();
}
//check to see if value has changed
if (!dbModel.ResidentLitresOrdered.Equals(residentOrder.Litres))
{
//get new ordered value
residentOrder.Litres = dbModel.ResidentLitresOrdered;
db.Commit();
//send an email just to notify in writing of the change.
SendOwnOrderQtyUpdateNotification();
}
return View(dbModel);
}
Basically if a resident order doesnt exist then we create one, this is the only place in the system where this would need to happen.
Should I still be stripping that code out into my repositories?
db is my IUnitOfWork btw

I would recommend that you create a "repository" to hide the details from the Controller action.
An "Upsert" method would allow this to be clearly and elegantly implemented hiding the details from the controller.

Related

An entity object cannot be referenced by multiple instances of IEntityChangeTracker when I try to create a post

I am trying create a post, I'm not quite sure why I get this error. An entity object cannot be referenced by multiple instances of IEntityChangeTracker, if someone could shed some light for me
This is the code in my Controller
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create(NewPostModel model)
{
var userId = User.Identity.GetUserId();
var user = await UserManager.FindByIdAsync(userId);
var post = BuildPost(model, user);
await PostService.Instance.Add(post);
return RedirectToAction("Index", "Post", new { id = post.Id });
}
private Post BuildPost(NewPostModel post, ApplicationUser user)
{
var now = DateTime.Now;
var forum = ForumServices.Instance.GetById(post.ForumId);
return new Post
{
Title = post.Title,
Content = post.Content,
Created = now,
Forum = forum,
User = user,
IsArchived = false
};
}
A here is my code in the PostService
public async Task Add(Post post)
{
using (var context = new ApplicationDbContext())
{
context.Posts.Add(post);
await context.SaveChangesAsync();
}
}
When I debug, the controller gets the model and the user, however when it gets to the await PostService.Instance.Add(post); it goes to the PostService then comes back to the await line in the controller then I get the error. I'm not sure what to do...
You get this exception because the forum and user property values belong to another Entity Framework context.
When you create your new Post you assign the Forum property to a forum instance. That instance came from ForumServices.Instance.GetById and is associated to a context. Then you add the Post object to another context, the forum instance is now associated to 2 contexts which is not possible.
To fix this, you can change the architecture to use a single EF context per request. It looks like you use a singleton (ForumServices.Instance) if you use .net core, you should have a look at how dependency injection works and use the AddScoped method to have a single EF context for the whole life time of the request. This is the solution I highly recommend.
If it is not possible to change the way context are created you can assign Id instead of full object
return new Post
{
Title = post.Title,
Content = post.Content,
Created = now,
ForumId = forum.ForumId,
UserId = user.UserId,
IsArchived = false
};
If it is still not possible you can return untracked entities. You can do this by using the AsNoTracking method or by using the QueryTrackingBehavior property of the context
context.Forums.AsNoTracking().First(f => f.ForumId == forumId);
// or
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
Thank you so much, I did as suggested by adding a ForumId and UserId to the models
return new Post
{
Title = post.Title,
Content = post.Content,
Created = now,
ForumId = post.ForumId,
UserId = user.Id,
IsArchived = false
};

how to update view with list of items in asp.net MVC

I have a view that displays a list of items and it has a search function.
The first time a user displays the page the page displays all items.
If they enter text in a search box and hit submit, the page calls an action that retrieves, from a database, a new list of items to be displayed on the page.
Here's my full controller. The very last line is where I'm not sure how to pass my params and if this is even the right way to go about it.
public IActionResult Index(IEnumerable<ItemListingModel> searchResults)
{
var categoryNames = _assets.GetCategoryNames();
var marketNames = _assets.GetMarketCategoryNames();
if (!searchResults.Any())
{
var assetItems = _assets.GetAll();
searchResults = assetItems.Select(result => new ItemListingModel
{
Id = result.Id,
ImageUrl = result.ImageUrl,
Title = result.Title,
Category = result.Category.Name,
Sku = result.Sku,
Location = result.Location,
Available = result.Available,
Notes = result.Notes
});
}
var assetModel = new ItemIndexModel()
{
Items = searchResults,
Categories = categoryNames,
Markets = marketNames
};
var searchForm = new SearchFormModel()
{
SearchQuery = "Enter search query"
};
ViewModel myModel = new ViewModel();
myModel.Item1 = assetModel;
myModel.Item2 = searchForm;
return View(myModel);
}
[HttpPost]
public IActionResult SearchSubmit(ViewModel search)
{
var results = _assets.searchInventoryAssets(search.Item2.SearchQuery, search.Item2.CategoryName, search.Item2.MarketCategoryName, search.Item2.ColumnName, search.Item2.ValueExpression).Select(result => new ItemListingModel
{
Id = result.Id,
ImageUrl = result.ImageUrl,
Title = result.Title,
Category = result.Category.Name,
Sku = result.Sku,
Location = result.Location,
Available = result.Available,
Notes = result.Notes
});
return //update index View with results list from this function, how can I achieve this?
}
Just a side note about coding standard: ViewModel is not a good name, I suggest renaming this class to SearchQueryViewModel
Why are you using HttpPost for your Search method?
We use HttpPost for sending data to the server and normally we want to persist this data... we can also send data to the server using QueryString in the URL, using HttpGet.
It depend on your application but generally it is better to send the search parameters in the QueryString instead of posting them in a form. The reason is:
If you use QueryString, then your url would look like:
www.myDomain/search?Item1=xyz&Item2=abc
This means your user can copy the above url and paste in an email and send it to his friend... the friend can click on the above url and he would see the same result.
But, currently you cannot do this, your url is always: www.myDomain/search and the search parameters are being passed through the form.
Again, it depend on your application, but if you want to change your code, it would look like this:
// remove [HttpPost] and use HttpGet request
public IActionResult Search(SearchQueryViewModel searchQuery)
{
// do youe search and get the result
var searchResult = MySearchService.Search(searchQuery);
// here DisplaySearchResult is the name of the view which displays the search result
return View(searchResult, "DisplaySearchResult")
}
Also, your Index action method does not look right to me... in MVC, Controller would build the Model and then pass this Model to the View. In case of your Index action, you are passing the searchResults to the Index method... you have already built the search results in another controller, they should be passed to the View not to another controller. Your current Index method is essentially a mapper, which does not seem right to me.

How to get the PK of a new item created in the database to use right away and update it's settings

I am using a web API with ASP.NET Core MVC, entityframework and angular as the front-end.
In my angular application I have Stepper component https://material.angular.io/components/stepper
In the first step I want to fill out my form and as soon as I click next I want to create the task and on the second form I want to update the settings for that newly created task. However, I need to get the PK of the newly created task to update the settings on my second form.
There is an api call to create a new task in the tTask table in sqlserver.
This is my API Post Method.
[ResponseType(typeof(CreatingTaskModel))]
public IHttpActionResult PosttTask(CreatingTaskModel CreatingTaskModel)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var newtTask= new tTask
{
TaskName = CreatingTaskModel.TaskName,
TaskDescription = CreatingTaskModel.TaskDescription,
EmailSubject = CreatingTaskModel.EmailSubject,
EmailBody = CreatingTaskModel.EmailBody,
FK_TaskTeam = CreatingTaskModel.tTaskTeam.pk_TaskTeam,
Enabled = CreatingTaskModel.Enabled ? 1:0,
};
db.tTasks.Add(newtTask);
db.SaveChanges();
var pk = new {id = CreatingTaskModel.PK_Task};
return Ok("Success");
}
This is the submit code in angular :
onSubmit()
{
if(this.taskForm.valid)
{
this.service.createTask(this.taskForm.value).subscribe((data) => {
debugger;
console.log(data)
})
}
}
I saw that return Ok() in my webapi returns a message and I was thinking of sorting the tTask table by decending order of after the db.SaveChanges();
and returning the last item that it find and sending it back in the Ok(pk) and then casting that into an integer on my client-side and using that to get the data to update it.
What is the correct way to do this? Should it be done in sql or on the webapi?
This is what I ended up doing:
var newObject = new
{
id = newtTask.PK_Task,
message = "Success"
};
return Ok(newObject);
and on angular I have this:
onSubmit(){
if(this.taskForm.valid) {
this.service.createTask(this.taskForm.value).subscribe((data) => {
if(data.message){
if(data.message == "Success"){
this.getRecipentData(data.id);
this.AdditionalRecipientForm.controls['FK_Task'].setValue(data.id);
this.pk_Task = data.id;
}
}
debugger;
console.log(data)
})
}
It just doesn't seem practical to do this, but it does the job. What do you guys think? Should I maybe instead of going to the serve twice maybe after it goes to the done filling out both forms submit them both? Like call create method in my API and then call my second API to update the data the was submitted in the second form. I am just looking for ideas or most common practice for these types of situations.
After you've added it to the database and called db.SaveChanges, the key will be assigned to the object. So, after db.SaveChanges, you should just be able to simply reference newtTask.Id.
So I assume you manually assign the id of the new task via PK_Task = CreatingTaskModel.PK_Task, and that it's indeed this id that gets saved to the Db. Therefore the id column should not be autoincrementing.
If that's true you can, as Louis said before, simply return your object or only the id if you're concerned about bandwidth.
You can do return new OkObjectResult(pk); or just use the shorthand: return Ok(pk);, which will also return an OkObjectResult with the pk object as payload.

Unable to update a column in a table on register

I am trying to teach myself asp.net.mvc and I started a project using the default template for MVC.
I changed the template a little, so when a register with the default action method I add the newly registered user id in another table called companies.
Basically, I added a new text field in the front end. When a user registers he types a number there. In the backend I look for a company from the companies table, which ID matches the number from the fronted. If there is a match I want to save the current user ID to a column in the companies table called CAId
Here is the whole action method (i made my modifications after the await):
public async Task<ActionResult> RegisterCA(RegisterCAEViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
var currentUser = UserManager.FindByName(user.UserName);
var roleresult = UserManager.AddToRole(currentUser.Id, "CompanyAdministrator");
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
var company = db.Companies.Find(model.CompanyCode);
// find the company by the id passed in the frontend.
if (company!=null)
{
company.CAId = currentUser.Id;
//set the id
db.Entry(company).State = EntityState.Modified;
db.SaveChanges();
}
return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
return View(model);
}
Here is what I tried in the method above :
var company = db.Companies.Find(model.CompanyCode);
// find the company by the id passed in the frontend.
if (company!=null)
{
company.CAId = currentUser.Id;
//set the id
db.Entry(company).State = EntityState.Modified;
db.SaveChanges();
When I register a user I do not get any errors. The user is registered, but the CAId field is not updated with the newly registered user ID.
Please, can somebody help and explain! I will be very grateful as I've been stuck with this for hours :(.
Try moving this line :
company.CAId = currentUser.Id;
After the line
db.SaveChanges();
The the currentauser id is an identity column it will be set only after it was saved to the db.
For anyone who will look for an answer to a similar question in future - the code in my question works absolutely fine. The problem was that in the view I was using the default Register action method and not the modified above! Thanks to all the people, who tried to help!
my guess, you need to attach your entity to the context underlying the set.
dbSet.Attach(company)
and then
context.Entry(company).State = EntityState.Modified;
db.SaveChanges();

mvc: Model.IsValid not working if Iam using Request.Form

Iam a beginner in MVC.
If iam using below code then Model.IsValid is not validating the object which in this case is Customer.
public ActionResult Submit()
{
Customer custObj = new Customer();
custObj.CustomerCode = Request.Form["CustomerCode"];
custObj.CustomerName = Request.Form["CustomerName"];
if (ModelState.IsValid)
return View("Load", obj);
else
return View("EnterCustomer");
}
While if Iam passing the Customer object in parameter then Model.IsValid is working perfectly.
public ActionResult Submit(Customer obj)
{
//Customer custObj = new Customer();
//custObj.CustomerCode = Request.Form["CustomerCode"];
//custObj.CustomerName = Request.Form["CustomerName"];
if (ModelState.IsValid)
return View("Load", obj);
else
return View("EnterCustomer");
}
Can any1 help me in getting to know the reason.
It doesn't work beause MVC never bound to the model itself. You manually overrode it so MVC has no clue whether the model is valid or not. It doesn't event know that custObj is the model.
ModelState.IsValid is set before your action method is called, so in your second example, when you allow MVC to bind to the model itself, it works. In the first, it doesn't work because you create the model and do manual binding to it.
Update
You can, however, also manually run the model validation by calling ValidateModel or TryValidateModel on the controller.
Documentation:
ValidateModel: https://msdn.microsoft.com/en-us/library/system.web.mvc.controller.validatemodel(v=vs.100).aspx
TryValidateModel: https://msdn.microsoft.com/en-us/library/system.web.mvc.controller.tryvalidatemodel(v=vs.100).aspx
As mentioned in other answers, your model is already validated before the action 'Submit' is called. So, when you are changing the model from inside your action, you will have to manually validated the model. You may use below code for it.
var context = new ValidationContext(custObj, serviceProvider: null, items: null);
var validationResults = new List<ValidationResult>();
bool isValid = Validator.TryValidateObject(custObj, context, validationResults, true);
if (isValid)
return View("Load", obj);
else
return View("EnterCustomer");
use below url for further details.
http://odetocode.com/blogs/scott/archive/2011/06/29/manual-validation-with-data-annotations.aspx

Categories

Resources