Can a Web Api controller render a view as a string? - asp.net-web-api

I would like to write a Web Api controller action that would send an email depending on results. I would like to use an MVC View or Partial view with a model of data to render the body of the email.
Is there a way to do this?
I would like something like this:
public class NotificationApiController : ApiController
{
private IMkpContext db;
public string ViewNotifications()
{
var dataModel = GetDataModel();
if (dataModel != null)
{
SendEmail(dataModel.ToAddress, dataModel.FromAddress, dataModel.Subject, RenderBody("viewName", dataModel);
}
return string.Empty;
}
}
Where RenderBody would look up the viewName, populate it with data from dataModel, and render the View as a string.

If you donĀ“t want to go with the RazorEngine approach suggested in the comments, you could define a class like this:
public static class ViewUtil
{
public static string RenderPartial(string partialName, object model)
{
var sw = new StringWriter();
var httpContext = new HttpContextWrapper(HttpContext.Current);
// point to an empty controller
var routeData = new RouteData();
routeData.Values.Add("controller", "EmptyController");
var controllerContext = new ControllerContext(new RequestContext(httpContext, routeData), new EmptyController());
var view = ViewEngines.Engines.FindPartialView(controllerContext, partialName).View;
view.Render(new ViewContext(controllerContext, view, new ViewDataDictionary { Model = model }, new TempDataDictionary(), sw), sw);
return sw.ToString();
}
}
class EmptyController : Controller { }

Related

.net web api AuthorizeAttribute pass parameter to the controller

A AuthorizedAttribute named as JwtAuthenticateAttribute is implemented and did something
It can be triggered for each request of the below API
[HttpGet]
[JwtAuthenticate]
public GetFaqListDS Index([FromUri] GetFaqListModel model)
{
List<MP_faqs> res = Faq.GetFaqList(model.curPage, model.pageSize);
var re = Request;
var hds = re.Headers;
GetFaqListDS ds = new GetFaqListDS();
ds.data = res;
return ds;
}
The question is, how to pass a value to this controller's function from the Attribute JwtAuthenticate.
P.S the value exists at the OnAuthorization of this attribute.
Thanks.
Add the required value in "actionContext.Request.Properties" in Onauthorize method and access these in controller.
Adding UserDetail Model :
actionContext.Request.Properties.Add("__user", new UserDetails(){userid=123, username="ABC"});
Retrieving values :
private static UserDetails GetUserDetailsFromRequest()
{
object tempVal;
try
{
var httpRequestMessage = (HttpRequestMessage)HttpContext.Current.Items["MS_HttpRequestMessage"];
httpRequestMessage.Properties.TryGetValue("__user", out tempVal);
if (tempVal == null) return null;
var user = (UserDetails)tempVal;
return user;
}
catch
{
}
return new UserDetails();
}
Hope this will helps you.

Manually build View Model from Request Data

I have a page that currently submits via Ajax to a controller method. MVC automatically converts the request data into my View Model type, and that's great.
Now I'm trying to change it so instead of an Ajax post, it makes a SignalR call instead. I want to submit the same data via SignalR (via $('form').serialize()), and then parse the data into my view model type.
Example:
//controller
public ActionResult MyMethod(MyViewModel vm){
//vm is automatically created from form data
}
//SignalR Hub
public void MyMethodViaSignalR(string formData){
//how can I turn formData or Context.Request into a MyViewModel?
}
//Make the SignalR call
myHub($('form').serialize());
I found a generic solution. I put this code in my Base Controller (you could copy it to any one controller as well), and it will build whatever view model you need.
protected class SignalRRequestJSon
{
public string H { get; set; }
public string M { get; set; }
public List<string> A { get; set; }
public int I { get; set; }
}
public T GetModel<T>(HttpRequest req) where T : class, new()
{
var obj = JsonConvert.DeserializeObject<SignalRRequestJSon>(req["data"]);
var stringWriter = new System.IO.StringWriter();
var httpResponse = new HttpResponse(stringWriter);
var url = req.UrlReferrer.ToString(); // this value doesn't matter, but needs to be a valid url.
var queryStringData = obj.A[0];
var httpRequest = new HttpRequest("", url, queryStringData);
var httpContext = new HttpContext(httpRequest, httpResponse);
var routeData = new RouteData();
routeData.Values.Add("controller", this.GetType().Name.ToLower().Replace("controller", ""));
routeData.Values.Add("action", "Arbitrary");
this.ControllerContext = new ControllerContext(new HttpContextWrapper(httpContext), routeData, this);
var valueProvider = new QueryStringValueProvider(this.ControllerContext);
this.ValueProvider = valueProvider;
var vm = new T();
UpdateModel(vm, valueProvider);
return vm;
}
public ActionResult Arbitrary<T>(T model)
{
return View();
}
Called like this from my SignalR hub:
var controller = DependencyResolver.Current.GetService<MyController>();
var vm = controller.GetModel<MyViewModel>(HttpContext.Current.Request);

Unit test WebApi2 passing header values

I am working on a project using WebApi2. With my test project I am using Moq and XUnit.
So far testing an api has been pretty straight forward to do a GET like
[Fact()]
public void GetCustomer()
{
var id = 2;
_customerMock.Setup(c => c.FindSingle(id))
.Returns(FakeCustomers()
.Single(cust => cust.Id == id));
var result = new CustomersController(_customerMock.Object).Get(id);
var negotiatedResult = result as OkContentActionResult<Customer>;
Assert.NotNull(negotiatedResult);
Assert.IsType<OkNegotiatedContentResult<Customer>>(negotiatedResult);
Assert.Equal(negotiatedResult.Content.Id,id);
}
Now I am moving onto something a little complicated where I need to access value from the request header.
I have created my own Ok() result by extending the IHttpActionResult
public OkContentActionResult(T content,HttpRequestMessage request)
{
_request = request;
_content = content;
}
This allows me to have a small helper that reads the header value from the request.
public virtual IHttpActionResult Post(Customer customer)
{
var header = RequestHeader.GetHeaderValue("customerId", this.Request);
if (header != "1234")
How am I meant to setup Moq with a dummy Request?
I have spent the last hour or so hunting for an example that allows me to do this with webapi however I cant seem to find anything.
So far.....and I am pretty sure its wrong for the api but I have
// arrange
var context = new Mock<HttpContextBase>();
var request = new Mock<HttpRequestBase>();
var headers = new NameValueCollection
{
{ "customerId", "111111" }
};
request.Setup(x => x.Headers).Returns(headers);
request.Setup(x => x.HttpMethod).Returns("GET");
request.Setup(x => x.Url).Returns(new Uri("http://foo.com"));
request.Setup(x => x.RawUrl).Returns("/foo");
context.Setup(x => x.Request).Returns(request.Object);
var controller = new Mock<ControllerBase>();
_customerController = new CustomerController()
{
// Request = request,
};
I am not really sure what next I need to do as I havent needed to setup a mock HttpRequestBase in the past.
Can anyone suggest a good article or point me in the right direction?
Thank you!!!
I believe that you should avoid reading the headers in your controller for better separation of concerns (you don't need to read the Customer from request body in the controller right?) and testability.
How I will do it is create a CustomerId class (this is optional. see note below) and CustomerIdParameterBinding
public class CustomerId
{
public string Value { get; set; }
}
public class CustomerIdParameterBinding : HttpParameterBinding
{
public CustomerIdParameterBinding(HttpParameterDescriptor parameter)
: base(parameter)
{
}
public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken)
{
actionContext.ActionArguments[Descriptor.ParameterName] = new CustomerId { Value = GetIdOrNull(actionContext) };
return Task.FromResult(0);
}
private string GetIdOrNull(HttpActionContext actionContext)
{
IEnumerable<string> idValues;
if(actionContext.Request.Headers.TryGetValues("customerId", out idValues))
{
return idValues.First();
}
return null;
}
}
Writing up the CustomerIdParameterBinding
config.ParameterBindingRules.Add(p =>
{
return p.ParameterType == typeof(CustomerId) ? new CustomerIdParameterBinding(p) : null;
});
Then in my controller
public void Post(CustomerId id, Customer customer)
Testing the Parameter Binding
public void TestMethod()
{
var parameterName = "TestParam";
var expectedCustomerIdValue = "Yehey!";
//Arrange
var requestMessage = new HttpRequestMessage(HttpMethod.Post, "http://localhost/someUri");
requestMessage.Headers.Add("customerId", expectedCustomerIdValue );
var httpActionContext = new HttpActionContext
{
ControllerContext = new HttpControllerContext
{
Request = requestMessage
}
};
var stubParameterDescriptor = new Mock<HttpParameterDescriptor>();
stubParameterDescriptor.SetupGet(i => i.ParameterName).Returns(parameterName);
//Act
var customerIdParameterBinding = new CustomerIdParameterBinding(stubParameterDescriptor.Object);
customerIdParameterBinding.ExecuteBindingAsync(null, httpActionContext, (new CancellationTokenSource()).Token).Wait();
//Assert here
//httpActionContext.ActionArguments[parameterName] contains the CustomerId
}
Note: If you don't want to create a CustomerId class, you can annotate your parameter with a custom ParameterBindingAttribute. Like so
public void Post([CustomerId] string customerId, Customer customer)
See here on how to create a ParameterBindingAttribute

Using Url.Link with Attribute Routing in Webapi 2

I want to add a Location header to my http response when using webapi 2. The method below shows how to do this using a named route. Does anyone know if you can create the Url.Link using Attribute Routing feature that was released as part of webapi 2?
string uri = Url.Link("DefaultApi", new { id = reponse.Id });
httpResponse.Headers.Location = new Uri(uri);
Thanks in advance
You can use RouteName with Ur.Link when using attribute routing.
public class BooksController : ApiController
{
[Route("api/books/{id}", Name="GetBookById")]
public BookDto GetBook(int id)
{
// Implementation not shown...
}
[Route("api/books")]
public HttpResponseMessage Post(Book book)
{
// Validate and add book to database (not shown)
var response = Request.CreateResponse(HttpStatusCode.Created);
// Generate a link to the new book and set the Location header in the response.
string uri = Url.Link("GetBookById", new { id = book.BookId });
response.Headers.Location = new Uri(uri);
return response;
}
}
http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2#route-names
You can do:
[Route("{id}", Name="GetById")]
public IHttpActionResult Get(int id)
{
// Implementation...
}
public IHttpActionResult Post([FromBody] UsuarioViewModel usuarioViewModel)
{
if (!ModelState.IsValid)
return BadRequest();
var link = Url.Link("GetById", new { id = 1});
var content = "a object";
return Created(link, content);
}

Refactoring Switch statement in my Controller

I'm currently working on a MVC.NET 3 application; I recently attended a course by "Uncle Bob" Martin which has inspired me (shamed me?) into taking a hard look at my current development practice, particularly my refactoring habits.
So: a number of my routes conform to:
{controller}/{action}/{type}
Where type typically determines the type of ActionResult to be returned, e.g:
public class ExportController
{
public ActionResult Generate(String type, String parameters)
{
switch (type)
{
case "csv":
//do something
case "html":
//do something else
case "json":
//do yet another thing
}
}
}
Has anyone successfully applied the "replace switch with polymorhism" refactoring to code like this? Is this even a good idea? Would be great to hear your experiences with this kind of refactoring.
Thanks in advance!
The way I am looking at it, this controller action is screaming for a custom action result:
public class MyActionResult : ActionResult
{
public object Model { get; private set; }
public MyActionResult(object model)
{
if (model == null)
{
throw new ArgumentNullException("Haven't you heard of view models???");
}
Model = model;
}
public override void ExecuteResult(ControllerContext context)
{
// TODO: You could also use the context.HttpContext.Request.ContentType
// instead of this type route parameter
var typeValue = context.Controller.ValueProvider.GetValue("type");
var type = typeValue != null ? typeValue.AttemptedValue : null;
if (type == null)
{
throw new ArgumentNullException("Please specify a type");
}
var response = context.HttpContext.Response;
if (string.Equals("json", type, StringComparison.OrdinalIgnoreCase))
{
var serializer = new JavaScriptSerializer();
response.ContentType = "text/json";
response.Write(serializer.Serialize(Model));
}
else if (string.Equals("xml", type, StringComparison.OrdinalIgnoreCase))
{
var serializer = new XmlSerializer(Model.GetType());
response.ContentType = "text/xml";
serializer.Serialize(response.Output, Model);
}
else if (string.Equals("csv", type, StringComparison.OrdinalIgnoreCase))
{
// TODO:
}
else
{
throw new NotImplementedException(
string.Format(
"Sorry but \"{0}\" is not a supported. Try again later",
type
)
);
}
}
}
and then:
public ActionResult Generate(string parameters)
{
MyViewModel model = _repository.GetMeTheModel(parameters);
return new MyActionResult(model);
}
A controller should not care about how to serialize the data. That's not his responsibility. A controller shouldn't be doing any plumbing like this. He should focus on fetching domain models, mapping them to view models and passing those view models to view results.
If you wanted to "replace switch with polymorphism" in this case, you could create three overloaded Generate() ActionResult methods. Using custom model binding, make the Type parameter a strongly-typed enum called DataFormat (or whatever.) Then you'd have:
public ActionResult Generate(DataFormat.CSV, String parameters)
{
}
public ActionResult Generate(DataFormat.HTML, String parameters)
{
}
public ActionResult Generate(DataFormat.JSON, String parameters)
{
}
Once you get to this point, you can refactor further to get the repetition out of your Controller.

Resources