Rendering HTML Partial block in MVC3 - asp.net-mvc

I am trying to insert a custom header (held in a database) on my master page. The custom header changes on a user by user basis.
I'm slightly confused on how to attach a controller to a partial (if I even can do this). What I'm hoping to accomplish is to render a block of code from a specific controller called with some events.
public ActionResult GetHeader(Guid clientID)
{
string szHeader = GetTheme(ThemeType.Portal, JoloTheme.ThemeArea.Header, clientID);
return Content(szHeader, "text/html");
}
Is the controller I have created, but I'm not sure how to get this onto a subsection of a page I am currently writing (not in the same Controller).
Apologies if this is completely nonsense here, still learning MVC I'm afraid.

You should use Html.Action() helper in your _Layout.cshtml. I think you should use a Nullable Guid as a parameter because you won't be able to provide a valid clientID always.
public ActionResult GetHeader(Guid? clientID)
{
string szHeader = GetTheme(ThemeType.Portal, JoloTheme.ThemeArea.Header, clientID);
return Content(szHeader, "text/html");
}
Here's how you should call Html.Action helper in your _Layout.cshtml
#Html.Action("GetHeader",
"SomeController",
new { clientID = IsLoggedIn ? ClientID : (Guid?)null } )

Related

SurfaceController generate incorrect URL?

A Form is posted to a SurfaceController 'Submit' action. After saving to the database, it redirects to another action 'LeadContact', in the same controller (using RedirectToAction()), passing in 'Id' as a paramter. The model is then populated and passed to the 'LeadContact' view.
Not sure if I'm doing this correctly, but when 'LeadContact' renders in the browser, the URL shows as
http://localhost:50656/umbraco/Surface/HealthInsurance/LeadContact?leadOptionId=70`
while I'm expecting it to be
http://localhost:50656/HealthInsurance/LeadContact?leadOptionId=70
In short it adds /umbraco/SurfaceContact' into url.
Can you please advise how I can correct it and what I'm doing wrong ?
public ActionResult Submit(FormCollection form)
{
//Some logic and later redirect to another action 'LeadContact'
return RedirectToAction("LeadContact", new { leadOptionId = _id});
}
public ActionResult LeadContact(int leadOptionId)
{
MyViewModel model = new MyViewModel();
//Lines of code to populate data into model
return View("LeadContact", model);
}
Thanks for your help and sharing.
Check your project properties, under Web you most likely have a virtual path specified.

How to add WebApi in Asp.net MVC and then consume the WebAPI in the same application

I have achieved creating the WebApi itself and I can browse it from the browser and get the output.
The thing which is not working for me is that I am trying to consume the WebAPI from an MVC Controller, and I have written the code for calling the WebAPI in my "cshtml" view.
But it doesn't work, as I am getting the error in loading the page, I understand I am doing something wrong. So the first question would be: am I doing this correctly, or is it completely wrong to create a WebAPI part in an MVC Project and then try to consume it in the same MVC project from the controller?
To answer your question, it's actually "as designed" and recommended to have your WebAPI and MVC client inside the same project. This is why you have both a RouteConfig.cs and a WebApiConfig.cs inside your MVC project. RouteConfig.cs is for your MVC controllers, and WebApiConfig.cs is obviously for your Api Controllers.
To have both in the same project is easy. What I do is add a folder named "API" in my root, and place all my WebAPI controllers in there. Keep in mind, and I'm sure you know, that the only difference between a WebAPI controller and an MVC controller is that a WebAPI controller inherits ApiController which is part of System.Web.Http (I believe) whereas an MVC controller inherits Controller which is part of System.Web.MVC.
Below is the proper way to make GET/PUT/DELETE/POST requests TO your WebAPI FROM an MVC front end. It doesn't matter if it's in the same project or not because you specify the WebAPI URL in your controller's constructor. If your WebAPI is on a different server than your front end MVC app, you will need to enable CORS support, which is a feature available in WebAPI version 2 and above.
This is the proper way to call a WebAPI from your front end, MVC client.
In your controller page, remove anything that has to do with DbContext, Entity Framework, etc. The reason is by default, the controller will want to perform CRUD operations by calling the DbContext, and we don't want this. We want to call the WebAPI instead to do this. When I refer to "Controller", I'm referring to the MVC controller, not the WebAPI controller.
First and foremost, declare some member variables in your MVC controller. The rest of your MVC controller will utilize these:
HttpClient client = new HttpClient();
HttpResponseMessage response = new HttpResponseMessage();
Uri contactUri = null;
In your MVC controller, create a constructor for your controller, as such:
public ContactController()
{
// set base address of WebAPI depending on your current environment
// the URL below, if the API is in the same project, will be something
// like "http://server/YourProjectName" - replace server with either
// "localhost", etc.
client.BaseAddress = new Uri("http://server/YourAPI/");
// Add an Accept header for JSON format.
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
}
Replace the Index action's code with something like the following. Note that the only relevant pieces are the client.GetAsync() call and the var contacts assignment. Everything else is not necessary for the context of this problem. The value inside the client.GetAsync() should be the name of your controller, prepended by any custom routing you set up in your WebApiConfig.cs - in my case, I added the api part in my route to distinguish between API calls and normal calls:
public ActionResult Index()
{
response = client.GetAsync("api/contact").Result;
if (response.IsSuccessStatusCode)
{
var contacts = response.Content.ReadAsAsync<IEnumerable<Contact>>().Result;
return View(contacts);
}
else
{
// add something here to tell the user hey, something went wrong
return RedirectToAction("Index");
}
}
Replace the Create action (the HttpPost action) with something like the following. Again, the only important piece is the client.PostAsJsonAsync() part - this is what calls the WebAPI's POST action which takes care of, in my case, inserting a new record into the database:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Contact contact)
{
// Create a new product
response = client.PostAsJsonAsync("api/contact", contact).Result;
if (response.IsSuccessStatusCode)
{
return RedirectToAction("Index");
}
else
{
// add something here to tell the user hey, something went wrong
return RedirectToAction("Index");
}
}
Replace the Edit action (the non-HttpPost action) with something like the following. This was a little tricky because in order to edit, you had to retrieve the record first, so basically, the HttpPost version of Edit will contain somewhat similar code, with an additional line of code that performs the edit POST (PUT). Below, we're getting the response from the WebAPI by passing it a specific record ID. So, just like for Index (GET), we are doing the same thing only passing in the ID so we only get back one record. Then, we cast the response to an actual object that can be operated on in the View:
public ActionResult Edit(int id = 0)
{
response = client.GetAsync(string.Format("api/contact/{0}", id)).Result;
Contact contact = response.Content.ReadAsAsync<Contact>().Result;
if (contact == null)
{
return HttpNotFound();
}
return View(contact);
}
Replace the Edit action (the HttpPost action) with something like the following. Below, we're getting the record to be edited by calling client.GetAsync() and passing in the primary key as a parameter (contact_id). Then, we're getting the RequestUri from that response and saving it. Then, we're calling client.PutAsJsonAsync() and passing in the Uri.PathAndQuery (what we just saved) as well as the object to be edited.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Contact contact)
{
response = client.GetAsync(string.Format("api/contact/{0}", contact.contact_id)).Result;
contactUri = response.RequestMessage.RequestUri;
response = client.PutAsJsonAsync(contactUri.PathAndQuery, contact).Result;
if (response.IsSuccessStatusCode)
{
return RedirectToAction("Index");
}
else
{
// add something here to tell the user hey, something went wrong
return RedirectToAction("Index");
}
}
Replace the Delete action (the non-HttpPost action) with something like the following. So again, we're getting the record from the database by simply calling client.GetAsync() and casting it to an actual object my app knows of.
public ActionResult Delete(int id = 0)
{
response = client.GetAsync(string.Format("api/contact/{0}", id)).Result;
Contact contact = response.Content.ReadAsAsync<Contact>().Result;
if (contact == null)
{
return HttpNotFound();
}
return View(contact);
}
Finally, replace the Delete action (the HttpPost action) with something like the following. Again, we're doing something similar to that of the Edit action. We are getting the record to be deleted, casting it to an object, and then passing that object into a client.DeleteAsync() call, as shown below.
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
response = client.GetAsync(string.Format("api/contact/{0}", id)).Result;
contactUri = response.RequestMessage.RequestUri;
response = client.DeleteAsync(contactUri).Result;
return RedirectToAction("Index");
}

MVC _Layout with Action using Session -order of loading is an issue for me

All,
I have an MVC _Layout.cshtml that calls:
#Html.Action("GetActionStrip", "Vehicles")
I also have a Controller that loads some views.
My Issue is that I have a controller action called GetVehicleDetails which gets a vehicle by ID.
The Action on:
#Html.Action("GetActionStrip", "Vehicles")
requires that GetVehicleDetails loads first as it need to put a vehicle id into session.
This is not working as:
#Html.Action("GetActionStrip", "Vehicles")
Loads before GetVehicleDetails.
#Html.Action("GetActionStrip", "Vehicles")
Needs to be on multiple views, that's why I put it in the _Layout file.
I can get it to work by putting:
#Html.Action("GetActionStrip", "Vehicles")
On every view I need it on and then they load in the correct order. ie.. the controller action GetVehicleDetails sets the vehicle id into session and then:
#Html.Action("GetActionStrip", "Vehicles")
Reads the session value.
Has anyone got any idea if I can do it the way I want or will I have to put my #Html.Action on every view which kind of breaks the DRY principle.
thanks
RuSs
Paul, I tried to write you a comment but the character limit killed me. Here is my comment:
Paul,
Thanks for the message. I understand what you have written but before I continue and try to implement something like this I just want to be sure you understand, fully, the scenario.
Will YOUR scenario cater for the fact that the code that needs the session value is called from an #Html.Action in my _Layout (master page so to speak) whereas I need my controller get action to receive a parameter and set this parameter into session.
From what I understand, _Layouts (master pages) load first so my #Html.Action would run and look for the session value. But, as this code is in a _Layout, it would run first and hence the GET on my controller has not yet set the session from the actions passed in parameter.
Note: my _Layout doesnt have it's own controller (not sure if this matters)
Thanks
RuSs
Something about the design is fundamentally incorrect. You shouldn't have different components being tightly coupled like this. The order shouldn't matter for which one comes first.
Here is how I might do what you're looking for. I'd create a model bound class that you can receive in your controller actions where you need the session value. The model will pull the session value from the database or wherever if it hasn't been set yet otherwise it uses the session value. Now order doesn't matter. Better yet you could make MySessionObject an interface and then you can mock it out in your test cases.
public interface IMySessionObject
{
int GetValueX();
}
public class MySessionObject : IModelBinder, IMySessionObject
{
private HttpContextBase _httpContext;
private MySessionObject(HttpContextBase httpContext)
{
_httpContext = httpContext;
}
public int GetValueX()
{
if (_httpContext.Session["x"] == null)
{
_httpContext.Session["x"] = 54; // Get the value here.
}
return (int)_httpContext.Session["x"];
}
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var context = controllerContext.HttpContext;
var obj = new MySessionObject(context);
return obj;
}
}
public class HomeController : Controller
{
public ActionResult Index(IMySessionObject obj)
{
ViewBag.X = obj.GetValueX();
ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application.";
return View();
}
public ActionResult About(IMySessionObject obj)
{
ViewBag.Message = "Your app description page.";
return View();
}
public ActionResult Contact(IMySessionObject obj)
{
ViewBag.Message = "Your contact page.";
return View();
}
}
Thanks Paul. I think your answer is correct without Sitecore CMS being part if the scenario. Ill vote you up but still doesn't fix my issue. Here is my other post which explains it better.
I don't expect you use Sitecore. https://stackoverflow.com/questions/14867915/order-of-loading-layout-and-url-driven-action-is-opposite-to-a-standard-non-sit ill try to find a way for Sitecore NOT to declaratively load my _layout before my MVC code runs.

Pass a big object to an action on a controller from a view

I want to pass a big object to a controller's action from a view. Like so:
View
<div>#Html.ActionLink("Send us an email", "Index",
"Email", new { o = #Model.Exception }, null)</div>
Controller
public class EmailController : Controller
{
[AllowAnonymous]
public ActionResult Index(object o)
{
new BaseServices.Emailer().SendEmail(o);
return View();
}
}
The thing is: the object being passed is so large that I guess that MVC is unable to make an argument out of that and add it to the route table/dictionary. So, my email controller's Index action is never called. The code bombs off somewhere in between.
No, you can't do this. ASP.NET MVC is not some magic. It relies on standard HTTP and HTML. And as you know in HTTP when you are using a GET request, there's no notion of .NET objects. You cannot ask how to pass an object in a web application because this is not defined.
There's a notion of query string parameters. So that's what you can pass => simple query string parameters:
#Html.ActionLink(
"Send us an email",
"Index",
"Email",
new { id = Model.Exception.Id, text = Model.Exception.Text },
null
)
Where the magic comes is that ASP.NET MVC will now use the 2 simple query string parameters (id and text) to map them to the corresponding properties of your view model inside the controller action.
But of course for this to work ASP.NET MVC needs to know the type of the model. You cannot just use object because this type doesn't have id nor text properties.
So:
public ActionResult Index(MyViewModel o)
Now but what about sending complex types? Well, the question that you have to ask to yourself is why on the first place this type was passed to the view? Was it because tfhe user was supposed to edit some of its properties? Is so then you should use an HTML form containing input fields allowing the user to edit them.
But since you have stuck this object into an anchor then what's the point? The server could fetch this object from wherever it fetched it in the first place. So all you need is to pass a simple id to the server:
#Html.ActionLink(
"Send us an email",
"Index",
"Email",
new { id = Model.Exception.Id },
null
)
and have the controller action take this id as parameter:
public ActionResult Index(int id)
Alright now you know the id => therefore you could retrieve the corresponding entity from wherever this entity is persisted.
Now some people might suggest you storing the object into the session before rendering the view and then retrieving this object from the session. Personally I am not a big fan of the session as it introduces state into the application. This means that you can never invoke the second action without first invoking the first action. This also means that you cannot bookmark the second action into the browser favorites. This also means that if you are running your application in a web-farm you can no longer store the session in-memory => you will have to use an out-of-process storage for this session. Sessions are way too much of a hassle.
You can't really pass it in the view.
Instead, consider storing the exception in TempData in the controller that renders the view....
public ActionResult DisplayErrorAndOptionToEmailIt()
{
TempData["LastError"] = m.Exception;
return View();
}
and then when the request comes in retrieve it from temp data and email it
public ActionResult SendTheEmail()
{
var e = TempData["LastError"] as Exception;
if (e != null)
{
EmailHelper.SendExceptionEmail(e);
}
return View();
}
On a side note, it's not the best practice to store complete objects. If possible, store only what you need:
public ActionResult DisplayErrorAndOptionToEmailIt()
{
TempData["LastError"] = m.Exception.Message;
return View();
}

Passing Information Between Controllers in ASP.Net-MVC

This is a duplicate of How to RedirectToAction in ASP.NET MVC without losing request data
Hi, I have come into a problem which is making me scratch my head a little bit. Basically I have a login page Login.aspx , which has username and password fields, as well as an important little checkbox. The login is handled in the AccountController Login method. The code currently is as follows:
[AcceptVerbs(HttpVerbs.Post)]
[SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
Justification =
"Needs to take same parameter type as Controller.Redirect()")]
public ActionResult LogOn(string userName, string password, string returnUrl,
bool sendStoredInfo)
{
if (!this.ValidateLogOn(userName, password)) {
return View();
}
this.FormsAuth.SignIn(userName, false);
if (!String.IsNullOrEmpty(returnUrl)) {
return Redirect(returnUrl);
} else {
return RedirectToAction("Index", "Home");
}
}
Basically, if the line return Redirect(returnUrl); fires, then it will end up in another controller, the OpenIDController, and it is that situation where the sendStoredInfo bool becomes important. But the problem is I have no reference to it when I'm in the OpenIDController. How can I send this value across?
Change the call to:
return RedirectToAction("LoginFailed", new { sendFlag = sendStoredInfo });
The controller action method signature could be something like:
public ActionResult LoginFailed(bool sendFlag)
{
...
}
Also consider using TempData to pass data from controller to controller. This can be advantageous as you wont have to expose the bool sendFlag interface potentially to the user.
Code in the first controller:
TempData["sendFlag"] = sendStoredInfo;
return RedirectToAction("LoginFailed");
Code in the second controller:
public ActionResult LoginFailed()
{
bool sendFlag = TempData.ContainsKey("sendFlag")? TempData["sendFlag"]: false;
}
Because of the nature of redirects, you can only perform a GET operation.
This means that you have to pass the parameter as part of the query string.
So you would redirect to a url like http://host/dir/page?sendStoredInfo=true
Then, you can chose to have it part of your method signature in the other controller, or, you can choose to access it directly using the HttpRequest exposed by the HttpContext for the operation.
You can also call the RedirectToAction, as per this previous question:
How to RedirectToAction in ASP.NET MVC without losing request data
As far as my knowledge serves me well, four different methods exist to handle passing data between controllers in asp.net MVC. They are 1. ViewData 2. ViewBag 3. TempData and 4. Sessions. If you may like a relatively good explanation besides a downloadable sample, please take a look at here

Resources