I'm using the MVC Mailer - I have this in my controller:
//
// POST: /ExhibitorInterest/Create
[HttpPost]
public ActionResult Create(ExhibitorInterest exhibitorinterest)
{
if (ModelState.IsValid)
{
db.ExhibitorInterests.Add(exhibitorinterest);
db.SaveChanges();
UserMailer.ExhibitorInterest().Send();
return RedirectToAction("Thankyou");
}
return View(exhibitorinterest);
}
But I want to pass the model exhibitorinterest to the mailer view:
The UserMailer.cs has:
public virtual MvcMailMessage ExhibitorInterest()
{
//ViewBag.Data = someObject;
return Populate(x =>
{
x.Subject = "ExhibitorInterest";
x.ViewName = "ExhibitorInterest";
x.To.Add("me#myemail.co.uk");
});
}
Any ideas how I get exhibitorinterest into the UserMailer.cs - so I can add it to the mail view please?
Thank you,
Mark
Think I figured it out - change the signature of IUSerMailer.cs to:
public interface IUserMailer
{
MvcMailMessage ExhibitorInterest(ExhibitorInterest exhibitorinterest);
And UserMail.cs to:
public virtual MvcMailMessage ExhibitorInterest(ExhibitorInterest exhibitorinterest)
{
ViewBag.Data = exhibitorinterest.xxxxxxx;
Hope it helps someone else.
Thanks, Mark
Related
I have this code:
[Route("Users")]
public class UserRegistrationController : Controller
{
[HttpGet("details/{userId}")]
public async Task<IActionResult> UserDetails(Guid userId)
{
// .....
}
[HttpPost("Save")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SaveUser(UserVM userVM)
{
// ......
return RedirectToAction("details", "Users", new { userId = userVM.UserId });
}
}
If I save the user redirectToAction generate userId as query string, this create an issue.
I mean instead of
https://localhost:5001/Users/Details/5de304c7-4c69-4819-c879-08d90306b555
redirect to action creates the URL as
https://localhost:5001/Users/Details?userId=5de304c7-4c69-4819-c879-08d90306b555
which causes a 404 error.
How do I solve this issue? I want to pass userId in route as below
https://localhost:5001/Users/Details/5de304c7-4c69-4819-c879-08d90306b555
Thanks in advance.
The issue was, the action method UserDetails need to add route [Route("details")] This will solve the issue.
[Route("Users")]
public class UserRegistrationController : Controller
{
[HttpGet("details/{userId}")]
[Route("details")]
public async Task<IActionResult> UserDetails(Guid userId)
{
// .....
}
[HttpPost("Save")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SaveUser(UserVM userVM)
{
// ......
return RedirectToAction("details", "Users", new { userId = userVM.UserId });
}
}
The rule is that controllers shouldn't have business logic, instead they should delegate it to the services. But when we do that, we can't handle all possible cases and return appropriate HTTP response.
Let's look at an example. Let's say that we are building some kind of a social network, and we need to create an endpoint for rating (liking or disliking) a post.
First let's take a look at an example where we delegate the logic to the service, this is our controller action:
public IActionResult Rate(long postId, RatingType ratingType)
{
var user = GetCurrentUser();
PostRating newPostRating = _postsService.Rate(postId, ratingType, user);
return Created(newPostRating);
}
Do you see a problem in this? What if there is no post with the given id, how would we return a not found response? What if user has no permissions to rate a post, how would we return a forbidden response?
PostsService.Rate can only return a new PostRating, but what about other cases? Well, we could throw an exception, we would need to create a lot of custom exception, so that we can map them to the appropriate HTTP responses. I don't like to use exceptions for this, I think there is a better way to do handle these cases instead of exceptions. Because I think that cases when post doesn't exist and when user has no permissions aren't exceptional at all, they're just normal cases just like rating a post successfully.
What I propose, is handling that logic in a controller instead. Because in my opinion, that should be a controllers responsibility anyway, to check all of the permissions before commiting an action. So this is how I would do it:
public IActionResult Rate(long postId, RatingType ratingType)
{
var user = GetCurrentUser();
var post = _postsRepository.GetByIdWithRatings(postId);
if (post == null)
return NotFound();
if (!_permissionService.CanRate(user, post))
return Forbidden();
PostRating newPostRating = new PostRating
{
Post = post,
Author = user,
Type = ratingType
};
_postRatingsRepository.Save(newPostRating);
return Created(newPostRating);
}
This is the way it should be done in my opinion but I bet that someone would say that this is too much logic for the controller, or that you shouldn't use a repository in it.
If you don't like using a repository in controller than where instead would you put a method that gets or saves posts? In service? So there would be PostsService.GetByIdWithRatings and PostsService.Save that would do nothing else but just call PostsRepository.GetByIdWithRatings and PostsRepository.Save. This so unnecessary and only causes boilerplate code.
Update:
Maybe someone will say to check the permissions using PostsService and then call PostsService.Rate. This is bad because it involves more unnecessary trips to database. For an example, it would probably be something like this:
public IActionResult Rate(long postId, RatingType ratingType)
{
var user = GetCurrentUser();
if(_postsService.Exists(postId))
return NotFound();
if(!_postsService.CanUserRate(user, postId))
return Forbidden();
PostRating newPostRating = _postsService.Rate(postId, ratingType, user);
return Created(newPostRating);
}
Do I even need to explain any further why this is bad?
There's a number of ways to handle this, but the closest thing to a "best practice" method is probably using a result class. For example, if your service method creates a rating and then returns that rating it created, you instead return an object that encapsulates the rating along with other relevant information, such as success status, error messages, if any etc.
public class RateResult
{
public bool Succeeded { get; internal set; }
public PostRating PostRating { get; internal set; }
public string[] Errors { get; internal set; }
}
Then, your controller code would become something like:
public IActionResult Rate(long postId, RatingType ratingType)
{
var user = GetCurrentUser();
var result = _postsService.Rate(postId, ratingType, user);
if (result.Succeeded)
{
return Created(result.PostRating);
}
else
{
// handle errors
}
}
What I did (just now) is created new class ApiResult
public class ApiResult
{
public int StatusCode { get; private set; } = 200;
public string RouteName { get; private set; }
public object RouteValues { get; private set; }
public object Content { get; private set; }
public void Ok(object content = null)
{
this.StatusCode = 200;
this.Content = content;
}
public void Created(string routeName, object routeValues, object content)
{
this.StatusCode = 201;
this.RouteName = routeName;
this.RouteValues = routeValues;
this.Content = content;
}
public void BadRequest(object content = null)
{
this.StatusCode = 400;
this.Content = content;
}
public void NotFound(object content = null)
{
this.StatusCode = 404;
this.Content = content;
}
public void InternalServerError(object content = null)
{
this.StatusCode = 500;
this.Content = content;
}
}
And a controller base class with a single method TranslateApiResult
public abstract class CommonControllerBase : ControllerBase
{
protected IActionResult TranslateApiResult(ApiResult result)
{
if (result.StatusCode == 201)
{
return CreatedAtAction(result.RouteName, result.RouteValues, result.Content);
}
else
{
return StatusCode(result.StatusCode, result.Content);
}
}
}
And now in controller I do:
[ApiController]
[Route("[controller]/[action]")]
public class MyController : CommonControllerBase
{
private readonly IMyApiServcie _service;
public MyController (
IMyApiServcie service)
{
_service = service;
}
[HttpGet]
public async Task<IActionResult> GetData()
{
return TranslateApiResult(await _service.GetData());
}
}
In your services you inject repositories and other dependencies:
public class MyApiServcie : IMyApiServcie
{
public async Task<ApiResult> GetData()
{
var result = new ApiResult();
// do something here
result.Ok("success");
return result;
}
}
Now, the reason for Api prefix before the Service is that this service is not meant to be the final service containing all logic.
At this point I would split the business logic into different domains so the services (or facades) would end up without Api prefix in them just to differentiate between i.e. CarService. Preferably these services will not know of anything related to API responses, statuses etc. It's up to you how implement it, though.
Hi I have agularjs resources below that is working fine.
return {
formDis: $resource('/api/pDisc/:id', { id: '#id' }, { update: { method: 'POST' } })
};
angularjs cotroller using the resource is
$scope.submit = function (form) {
console.log(form.dis.length);
console.log(form.dis);
for (var i = 0; i < form.dis.length; i++) {
pRepository.formDis.update(form.dis[i], function () {
{
alert("Saved");
}
});
};
};
WebConfig is
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
MVC Api that is recieving it is
// POST api/projDisc
public HttpResponseMessage Postproject_discussion(pDis pDis)
{
db.project_discussion.Add(pDis);
db.SaveChanges();
}
Class is
[DataContract(IsReference = false)]
[Serializable]
public partial class pDis
{
public int pDis_id { get; set; }
public int pro_id { get; set; }
public string message {get; set;}
public virtual pro pro { get; set; }
public virtual people people { get; set; }
}
}
When I run it I get error in my api at
db.project_discussion.Add(pDis);
Error is
An exception of type 'System.Data.Entity.Validation.DbEntityValidationException' occurred in EntityFramework.dll but was not handled in user code
I get this error because api is receiving empty object.
In angular controller I can see the objects in console being passed properly from form.
Once the object is submitted to the resource and from it to api there is something wrong that it ends up as empty object. Please let me know how to fix it.
Based on your comment:
I can see the right data being {"pro_id":"221","message":"sdsfsd"
pDis_id:""}
My best guess is that the model binder is not able to convert to the pDis type.
One thing I notice is that your property pDis_id is an int (non-nullable), and you are passing it pDis_id:"". I think the model binder will not know what to do in that case.
Try supplying an integer value for pDis_id, or not supplying it at all.
I am using Messenger to broadcast some information from a page code behind (I can't use eventtocommand) and messenger action is fring twice
here's my code
In viewModel
public CedareImpozitViewModel()
{
if (IsInDesignMode)
{
}
else
{
LoadListaPers();
LoadListaEntitati();
Messenger.Default.Register<EntitateMessage>(this, AdaugaEntitateNoua);
}
UpdCedareCommand = new RelayCommand(() => UpdCedare(), () => this.CanUpdCedare());
}
From page code behind
Messenger.Default.Send(new EntitateMessage(cedare) { cedarenoua = cedare, entNoua = entity});
and EntitateMessage class
public class EntitateMessage : GenericMessage<CedareImpozit>
{
public EntitateMessage(CedareImpozit parm)
: base(parm)
{
entNoua = null;
cedarenoua = null;
}
public CedareImpozit cedarenoua { get; set; }
public Entitate entNoua { get; set; }
}
I am not navigating twice from my page.
Any ideas ?
Thank you
The experience shows that when such a thing happens, the cause is always that the registration occurred twice. Place a breakpoint on the Register method call to verify.
Cheers,
Laurent
I am trying to use Async controller and am not able to figure out how would one validate the user input.
Following are the two async methods defined in my controller. Should I check for ModelState.IsValid in the SearchAsync method or SearchCompleted method. If SearchAsync then how will return the view result as its return type is void. If SearchCompleted then how will the method know about searchForm parameter.
[HttpPost]
[ValidateAntiForgeryToken]
public void SearchAsync(BusinessSearchForm searchForm)
{
AsyncManager.OutstandingOperations.Increment();
new Thread(() =>
{
var suggestions = _searchSvc.GetSuggestions(searchForm.BusinessName, searchForm.StreetAddress, searchForm.City, searchForm.PostalCode);
AsyncManager.Parameters["suggestions"] = suggestions;
AsyncManager.OutstandingOperations.Decrement();
}).Start();
}
public ActionResult SearchCompleted(IEnumerable<BusinessSuggestionBase> suggestions)
{
return View(suggestions);
}
The following seems to work for me. I end up checking for modelstate in both methods. Added the initial model as a param to the completed method. Asp.net Mvc seemed to persist the modelstate between the two methods
[HttpPost]
[ValidateAntiForgeryToken]
public void SearchAsync(BusinessSearchForm searchForm)
{
if (ModelState.IsValid)
{
AsyncManager.OutstandingOperations.Increment();
new Thread(() =>
{
if (ModelState.IsValid)
{
var suggestions = _searchBusinessSvc.GetSuggestions(searchForm.BusinessName, searchForm.StreetAddress, searchForm.City, searchForm.PostalCode);
AsyncManager.Parameters["suggestions"] = suggestions;
}
AsyncManager.Parameters["searchForm"] = searchForm;
AsyncManager.OutstandingOperations.Decrement();
}).Start();
}
}
public ActionResult SearchCompleted(BusinessSearchForm searchForm,IEnumerable<BusinessSuggestionBase> suggestions)
{
if (ModelState.IsValid)
{
TempData["suggestions"] = suggestions;
return RedirectToAction("SearchResult");
}
return View(searchForm);
}
You can use
AsyncManager.Parameters['ModelIsValid'] = false;
in the Async method, and
if(AsyncManager.Parameters['ModelIsValid'] == false) { ... }
in the Completed method to check and see if there was a validation issue. Simply do not increment the outstanding operations, and do not perform any further logic. The Completed method will fire, and you can check the the value.